Conceitos básicos - Amazon Nova

Conceitos básicos

Este guia mostra como implantar modelos personalizados do Amazon Nova nos endpoints em tempo real do SageMaker, configurar parâmetros de inferência e invocar seus modelos para testes.

Pré-requisitos

Confira abaixo os pré-requisitos para implantar modelos do Amazon Nova na inferência do SageMaker:

  • Crie uma Conta da AWS: se você ainda não tiver uma, consulte Criar uma conta da AWS.

  • Permissões necessárias do IAM - certifique-se de que seu usuário ou perfil do IAM tenha as seguintes políticas gerenciadas:

    • AmazonSageMakerFullAccess

    • AmazonS3FullAccess

  • Versões obrigatórias de SDKs/CLI - as seguintes versões de SDK foram testadas e validadas com modelos do Amazon Nova na inferência do SageMaker:

    • SageMaker Python SDK v3.0.0+ (sagemaker>=3.0.0) para abordagem de API baseada em recursos.

    • Boto3 versão 1.35.0+ (boto3>=1.35.0) para chamadas diretas de API. Os exemplos neste guia usam essa abordagem.

Etapa 1: configurar as credenciais da AWS

Configure suas credenciais da AWS usando um dos seguintes métodos:

Opção 1: AWS CLI (recomendado)

aws configure

Insira o ID da chave de acesso da AWS, a chave secreta e nome da região padrão quando solicitado.

Opção 2: arquivo de credenciais da AWS

Crie ou edite ~/.aws/credentials:

[default] aws_access_key_id = YOUR_ACCESS_KEY aws_secret_access_key = YOUR_SECRET_KEY

Opção 3: variáveis de ambiente

export AWS_ACCESS_KEY_ID=your_access_key export AWS_SECRET_ACCESS_KEY=your_secret_key
nota

Para obter mais informações sobre as credenciais da AWS, consulte Definições do arquivo de credenciais e configurações.

Inicializar clientes da AWS

Crie um script ou caderno Python com o código abaixo para inicializar o AWS SDK e verificar suas credenciais:

import boto3 # AWS Configuration - Update these for your environment REGION = "us-east-1" # Supported regions: us-east-1, us-west-2 AWS_ACCOUNT_ID = "YOUR_ACCOUNT_ID" # Replace with your AWS account ID # Initialize AWS clients using default credential chain sagemaker = boto3.client('sagemaker', region_name=REGION) sts = boto3.client('sts') # Verify credentials try: identity = sts.get_caller_identity() print(f"Successfully authenticated to AWS Account: {identity['Account']}") if identity['Account'] != AWS_ACCOUNT_ID: print(f"Warning: Connected to account {identity['Account']}, expected {AWS_ACCOUNT_ID}") except Exception as e: print(f"Failed to authenticate: {e}") print("Please verify your credentials are configured correctly.")

Se a autenticação tiver êxito, você deverá ver um resultado confirmando o ID da sua conta da AWS.

Etapa 2: criar um perfil de execução do SageMaker

Um perfil de execução do SageMaker é um perfil do IAM que concede ao SageMaker permissões para acessar recursos da AWS em seu nome, como buckets do Amazon S3 para artefatos de modelos e o CloudWatch para registro em log.

Criação do perfil de execução

nota

A criação de perfis do IAM requer as permissões iam:CreateRole e iam:AttachRolePolicy. Antes de continuar, certifique-se de que seu usuário ou perfil do IAM tenha essas permissões.

O código abaixo cria um perfil do IAM com as permissões necessárias para implantar modelos personalizados do Amazon Nova:

import json # Create SageMaker Execution Role role_name = f"SageMakerInference-ExecutionRole-{AWS_ACCOUNT_ID}" trust_policy = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": {"Service": "sagemaker.amazonaws.com"}, "Action": "sts:AssumeRole" } ] } iam = boto3.client('iam', region_name=REGION) # Create the role role_response = iam.create_role( RoleName=role_name, AssumeRolePolicyDocument=json.dumps(trust_policy), Description='SageMaker execution role with S3 and SageMaker access' ) # Attach required policies iam.attach_role_policy( RoleName=role_name, PolicyArn='arn:aws:iam::aws:policy/AmazonSageMakerFullAccess' ) iam.attach_role_policy( RoleName=role_name, PolicyArn='arn:aws:iam::aws:policy/AmazonS3FullAccess' ) SAGEMAKER_EXECUTION_ROLE_ARN = role_response['Role']['Arn'] print(f"Created SageMaker execution role: {SAGEMAKER_EXECUTION_ROLE_ARN}")

Uso de um perfil de execução existente (opcional)

Se já tiver um perfil de execução do SageMaker, você poderá usá-lo:

# Replace with your existing role ARN SAGEMAKER_EXECUTION_ROLE_ARN = "arn:aws:iam::YOUR_ACCOUNT_ID:role/YOUR_EXISTING_ROLE_NAME"

Para encontrar perfis existentes do SageMaker em sua conta:

iam = boto3.client('iam', region_name=REGION) response = iam.list_roles() sagemaker_roles = [role for role in response['Roles'] if 'SageMaker' in role['RoleName']] for role in sagemaker_roles: print(f"{role['RoleName']}: {role['Arn']}")
Importante

O perfil de execução deve ter uma relação de confiança com sagemaker.amazonaws.com e permissões para acessar os recursos do Amazon S3 e do SageMaker.

Para obter mais informações sobre perfis de execução do SageMaker, consulte Perfis do SageMaker.

Etapa 3: configurar parâmetros de modelos

Configure os parâmetros de implantação para seu modelo do Amazon Nova. Essas configurações controlam o comportamento do modelo, a alocação de recursos e as características de inferência.

Parâmetros obrigatórios

  • IMAGE: o URI da imagem do contêiner do Docker para o contêiner de inferência do Amazon Nova. Será fornecido pela AWS.

  • CONTEXT_LENGTH: tamanho do contexto do modelo.

  • MAX_CONCURRENCY: número máximo de sequências por iteração; define o limite de quantas solicitações (prompts) de usuários individuais podem ser processadas simultaneamente em um único lote na GPU. Intervalo: inteiro maior que 0.

Parâmetros opcionais de geração

  • DEFAULT_TEMPERATURE: controla a randomização na geração. Intervalo: de 0,0 a 2,0 (0,0 = determinístico, maior = mais randomizado).

  • DEFAULT_TOP_P: limite de amostragem de núcleo para a seleção de tokens. Intervalo: de 1e-10 a 1,0.

  • DEFAULT_TOP_K: limita a seleção de tokens aos K tokens mais prováveis. Intervalo: inteiro -1 ou maior (-1 = sem limite).

  • DEFAULT_MAX_NEW_TOKENS: número máximo de tokens a serem gerados na resposta (ou seja, máximo de tokens de saída). Intervalo: inteiro 1 ou maior.

  • DEFAULT_LOGPROBS: número de probabilidades logarítmicas a serem retornadas por token. Intervalo: inteiro de 1 a 20.

Configure sua implantação

# AWS Configuration REGION = "us-east-1" # Must match region from Step 1 # ECR Account mapping by region ECR_ACCOUNT_MAP = { "us-east-1": "708977205387", "us-west-2": "176779409107" } # Container Image - Replace with the image URI provided by your AWS contact # Two image tags are available (both point to the same image): IMAGE_LATEST = f"{ECR_ACCOUNT_MAP[REGION]}.dkr.ecr.{REGION}.amazonaws.com/nova-inference-repo:SM-Inference-latest" IMAGE_VERSIONED = f"{ECR_ACCOUNT_MAP[REGION]}.dkr.ecr.{REGION}.amazonaws.com/nova-inference-repo:v1.0.0" # Use the versioned tag for production deployments (recommended) IMAGE = IMAGE_VERSIONED print(f"IMAGE = {IMAGE}") print(f"Available tags:") print(f" Latest: {IMAGE_LATEST}") print(f" Versioned: {IMAGE_VERSIONED}") # Model Parameters CONTEXT_LENGTH = "8000" # Maximum total context length MAX_CONCURRENCY = "16" # Maximum concurrent sequences # Optional: Default generation parameters (uncomment to use) DEFAULT_TEMPERATURE = "0.0" # Deterministic output DEFAULT_TOP_P = "1.0" # Consider all tokens # DEFAULT_TOP_K = "50" # Uncomment to limit to top 50 tokens # DEFAULT_MAX_NEW_TOKENS = "2048" # Uncomment to set max output tokens # DEFAULT_LOGPROBS = "1" # Uncomment to enable log probabilities # Build environment variables for the container environment = { 'CONTEXT_LENGTH': CONTEXT_LENGTH, 'MAX_CONCURRENCY': MAX_CONCURRENCY, } # Add optional parameters if defined if 'DEFAULT_TEMPERATURE' in globals(): environment['DEFAULT_TEMPERATURE'] = DEFAULT_TEMPERATURE if 'DEFAULT_TOP_P' in globals(): environment['DEFAULT_TOP_P'] = DEFAULT_TOP_P if 'DEFAULT_TOP_K' in globals(): environment['DEFAULT_TOP_K'] = DEFAULT_TOP_K if 'DEFAULT_MAX_NEW_TOKENS' in globals(): environment['DEFAULT_MAX_NEW_TOKENS'] = DEFAULT_MAX_NEW_TOKENS if 'DEFAULT_LOGPROBS' in globals(): environment['DEFAULT_LOGPROBS'] = DEFAULT_LOGPROBS print("Environment configuration:") for key, value in environment.items(): print(f" {key}: {value}")

Configurar parâmetros específicos da implantação

Agora configure os parâmetros específicos para a implantação de modelos do Amazon Nova, incluindo o local dos artefatos do modelo e a seleção do tipo de instância.

Definir identificador de implantação

# Deployment identifier - use a descriptive name for your use case JOB_NAME = "my-nova-deployment"

Especificar o local dos artefatos dos modelos

Forneça o URI do Amazon S3 em que os artefatos treinados dos modelos do Amazon Nova foram armazenados. Deve ser o local da saída da tarefa de treinamento de modelo ou de ajuste fino.

# S3 location of your trained Nova model artifacts # Replace with your model's S3 URI - must end with / MODEL_S3_LOCATION = "s3://your-bucket-name/path/to/model/artifacts/"

Selecionar a variante do modelo e o tipo de instância

# Configure model variant and instance type TESTCASE = { "model": "micro", # Options: micro, lite, lite2 "instance": "ml.g5.12xlarge" # Refer to "Supported models and instances" section } # Generate resource names INSTANCE_TYPE = TESTCASE["instance"] MODEL_NAME = JOB_NAME + "-" + TESTCASE["model"] + "-" + INSTANCE_TYPE.replace(".", "-") ENDPOINT_CONFIG_NAME = MODEL_NAME + "-Config" ENDPOINT_NAME = MODEL_NAME + "-Endpoint" print(f"Model Name: {MODEL_NAME}") print(f"Endpoint Config: {ENDPOINT_CONFIG_NAME}") print(f"Endpoint Name: {ENDPOINT_NAME}")

Convenções de nomenclatura

O código gera automaticamente nomes consistentes para os recursos da AWS:

  • Nome do modelo: .: {JOB_NAME}-{model}-{instance-type}

  • Configuração de endpoint: {MODEL_NAME}-Config

  • Nome do endpoint: {MODEL_NAME}-Endpoint

Etapa 4: criar a configuração do modelo e endpoint do SageMaker

Nesta etapa, você criará dois recursos essenciais: um objeto de modelo do SageMaker que faz referência aos artefatos dos modelos do Amazon Nova e uma configuração do endpoint que define como o modelo será implantado.

Modelo do SageMaker: um objeto de modelo que empacota a imagem do contêiner de inferência, o local dos artefatos do modelo e a configuração do ambiente. Este é um recurso reutilizável que pode ser implantado em vários endpoints.

Configuração do endpoint: define as configurações de infraestrutura para implantação, incluindo tipo de instância, contagem de instâncias e variantes do modelo. Isso permite que você gerencie as configurações de implantação separadamente do próprio modelo.

Criar o modelo do SageMaker

O código abaixo cria um modelo do SageMaker que faz referência aos artefatos do seu modelo do Amazon Nova:

try: model_response = sagemaker.create_model( ModelName=MODEL_NAME, PrimaryContainer={ 'Image': IMAGE, 'ModelDataSource': { 'S3DataSource': { 'S3Uri': MODEL_S3_LOCATION, 'S3DataType': 'S3Prefix', 'CompressionType': 'None' } }, 'Environment': environment }, ExecutionRoleArn=SAGEMAKER_EXECUTION_ROLE_ARN, EnableNetworkIsolation=True ) print("Model created successfully!") print(f"Model ARN: {model_response['ModelArn']}") except sagemaker.exceptions.ClientError as e: print(f"Error creating model: {e}")

Principais parâmetros:

  • ModelName: identificador exclusivo do seu modelo

  • Image: URI de imagem de contêiner do Docker para inferência do Amazon Nova

  • ModelDataSource: local do Amazon S3 dos seus artefatos de modelos

  • Environment: variáveis de ambiente configuradas na Etapa 3

  • ExecutionRoleArn: perfil do IAM da Etapa 2

  • EnableNetworkIsolation: defina como True para aumentar a segurança (impede que o contêiner faça chamadas de rede de saída)

Criar a configuração do endpoint

Em seguida, crie uma configuração do endpoint que defina sua infraestrutura de implantação:

# Create Endpoint Configuration try: production_variant = { 'VariantName': 'primary', 'ModelName': MODEL_NAME, 'InitialInstanceCount': 1, 'InstanceType': INSTANCE_TYPE, } config_response = sagemaker.create_endpoint_config( EndpointConfigName=ENDPOINT_CONFIG_NAME, ProductionVariants=[production_variant] ) print("Endpoint configuration created successfully!") print(f"Config ARN: {config_response['EndpointConfigArn']}") except sagemaker.exceptions.ClientError as e: print(f"Error creating endpoint configuration: {e}")

Principais parâmetros:

  • VariantName: identificador para essa variante de modelo (use “primary” para implantações de modelo único)

  • ModelName: faz referência ao modelo criado acima

  • InitialInstanceCount: número de instâncias a serem implantadas (comece com 1 e escale posteriormente, se necessário)

  • InstanceType: tipo de instância de ML selecionado na Etapa 3

Verificar a criação de recursos

Você pode verificar se os recursos foram criados com êxito:

# Describe the model model_info = sagemaker.describe_model(ModelName=MODEL_NAME) print(f"Model Status: {model_info['ModelName']} created") # Describe the endpoint configuration config_info = sagemaker.describe_endpoint_config(EndpointConfigName=ENDPOINT_CONFIG_NAME) print(f"Endpoint Config Status: {config_info['EndpointConfigName']} created")

Etapa 5: implantar o endpoint

A próxima etapa é implantar seu modelo do Amazon Nova criando um endpoint em tempo real do SageMaker. Esse endpoint hospedará seu modelo e fornecerá um endpoint HTTPS seguro para fazer solicitações de inferência.

A criação do endpoint normalmente leva de 15 a 30 minutos, pois a AWS provisiona a infraestrutura, faz o download dos artefatos do modelo e inicializa o contêiner de inferência.

Criar o endpoint

import time try: endpoint_response = sagemaker.create_endpoint( EndpointName=ENDPOINT_NAME, EndpointConfigName=ENDPOINT_CONFIG_NAME ) print("Endpoint creation initiated successfully!") print(f"Endpoint ARN: {endpoint_response['EndpointArn']}") except Exception as e: print(f"Error creating endpoint: {e}")

Monitorar a criação do endpoint

O código abaixo pesquisa o status do endpoint até a conclusão da implantação:

# Monitor endpoint creation progress print("Waiting for endpoint creation to complete...") print("This typically takes 15-30 minutes...\n") while True: try: response = sagemaker.describe_endpoint(EndpointName=ENDPOINT_NAME) status = response['EndpointStatus'] if status == 'Creating': print(f"⏳ Status: {status} - Provisioning infrastructure and loading model...") elif status == 'InService': print(f"✅ Status: {status}") print("\nEndpoint creation completed successfully!") print(f"Endpoint Name: {ENDPOINT_NAME}") print(f"Endpoint ARN: {response['EndpointArn']}") break elif status == 'Failed': print(f"❌ Status: {status}") print(f"Failure Reason: {response.get('FailureReason', 'Unknown')}") print("\nFull response:") print(response) break else: print(f"Status: {status}") except Exception as e: print(f"Error checking endpoint status: {e}") break time.sleep(30) # Check every 30 seconds

Verificar se o endpoint está pronto

Quando o endpoint estiver InService, você poderá verificar sua configuração:

# Get detailed endpoint information endpoint_info = sagemaker.describe_endpoint(EndpointName=ENDPOINT_NAME) print("\n=== Endpoint Details ===") print(f"Endpoint Name: {endpoint_info['EndpointName']}") print(f"Endpoint ARN: {endpoint_info['EndpointArn']}") print(f"Status: {endpoint_info['EndpointStatus']}") print(f"Creation Time: {endpoint_info['CreationTime']}") print(f"Last Modified: {endpoint_info['LastModifiedTime']}") # Get endpoint config for instance type details endpoint_config_name = endpoint_info['EndpointConfigName'] endpoint_config = sagemaker.describe_endpoint_config(EndpointConfigName=endpoint_config_name) # Display production variant details for variant in endpoint_info['ProductionVariants']: print(f"\nProduction Variant: {variant['VariantName']}") print(f" Current Instance Count: {variant['CurrentInstanceCount']}") print(f" Desired Instance Count: {variant['DesiredInstanceCount']}") # Get instance type from endpoint config for config_variant in endpoint_config['ProductionVariants']: if config_variant['VariantName'] == variant['VariantName']: print(f" Instance Type: {config_variant['InstanceType']}") break

Solução de problemas de falhas na criação de endpoints

Motivos de falha comuns:

  • Capacidade insuficiente: o tipo de instância solicitada não está disponível na sua região.

    • Solução: tente um tipo de instância diferente ou solicite um aumento da cota

  • Permissões do IAM: o perfil de execução não tem as permissões necessárias

    • Solução: verifique se o perfil tem acesso aos artefatos dos modelos do Amazon S3 e às permissões necessárias do SageMaker

  • Artefatos do modelo não encontrados: o URI do Amazon S3 está incorreto ou inacessível

    • Solução: verifique o URI do Amazon S3, as permissões do bucket e se você está na região correta

  • Limites de recursos: limites da conta excedidos para endpoints ou instâncias

    • Solução: solicite um aumento de cota de serviço por meio do Service Quotas ou do AWS Support

nota

Se você precisar excluir um endpoint com falha e começar de novo:

sagemaker.delete_endpoint(EndpointName=ENDPOINT_NAME)

Etapa 6: invocar o endpoint

Quando seu endpoint estiver InService, você poderá enviar solicitações de inferência para gerar predições do seu modelo do Amazon Nova. O SageMaker oferece suporte a endpoints síncronos (em tempo real com os modos de streaming/não streaming) e endpoints assíncronos (baseados no Amazon S3 para processamento em lote).

Configurar o cliente de runtime

Crie um cliente de runtime do SageMaker com as configurações de tempo limite apropriadas:

import json import boto3 import botocore from botocore.exceptions import ClientError # Configure client with appropriate timeouts config = botocore.config.Config( read_timeout=120, # Maximum time to wait for response connect_timeout=10, # Maximum time to establish connection retries={'max_attempts': 3} # Number of retry attempts ) # Create SageMaker Runtime client runtime_client = boto3.client('sagemaker-runtime', config=config, region_name=REGION)

Criar uma função de inferência universal

A função abaixo processa solicitações com ou sem streaming:

def invoke_nova_endpoint(request_body): """ Invoke Nova endpoint with automatic streaming detection. Args: request_body (dict): Request payload containing prompt and parameters Returns: dict: Response from the model (for non-streaming requests) None: For streaming requests (prints output directly) """ body = json.dumps(request_body) is_streaming = request_body.get("stream", False) try: print(f"Invoking endpoint ({'streaming' if is_streaming else 'non-streaming'})...") if is_streaming: response = runtime_client.invoke_endpoint_with_response_stream( EndpointName=ENDPOINT_NAME, ContentType='application/json', Body=body ) event_stream = response['Body'] for event in event_stream: if 'PayloadPart' in event: chunk = event['PayloadPart'] if 'Bytes' in chunk: data = chunk['Bytes'].decode() print("Chunk:", data) else: # Non-streaming inference response = runtime_client.invoke_endpoint( EndpointName=ENDPOINT_NAME, ContentType='application/json', Accept='application/json', Body=body ) response_body = response['Body'].read().decode('utf-8') result = json.loads(response_body) print("✅ Response received successfully") return result except ClientError as e: error_code = e.response['Error']['Code'] error_message = e.response['Error']['Message'] print(f"❌ AWS Error: {error_code} - {error_message}") except Exception as e: print(f"❌ Unexpected error: {str(e)}")

Exemplo 1: conclusão de chat sem streaming

Use o formato de chat para interações conversacionais:

# Non-streaming chat request chat_request = { "messages": [ {"role": "user", "content": "Hello! How are you?"} ], "max_tokens": 100, "max_completion_tokens": 100, # Alternative to max_tokens "stream": False, "temperature": 0.7, "top_p": 0.9, "top_k": 50, "logprobs": True, "top_logprobs": 3, "allowed_token_ids": None, # List of allowed token IDs "truncate_prompt_tokens": None, # Truncate prompt to this many tokens "stream_options": None } response = invoke_nova_endpoint(chat_request)

Exemplo 2: conclusão de texto simples

Use o formato de conclusão para geração de texto simples:

# Simple completion request completion_request = { "prompt": "The capital of France is", "max_tokens": 50, "stream": False, "temperature": 0.0, "top_p": 1.0, "top_k": -1, # -1 means no limit "logprobs": 3, # Number of log probabilities to return "allowed_token_ids": None, # List of allowed token IDs "truncate_prompt_tokens": None, # Truncate prompt to this many tokens "stream_options": None } response = invoke_nova_endpoint(completion_request)

Exemplo 3: conclusão de chat com streaming

# Streaming chat request streaming_request = { "messages": [ {"role": "user", "content": "Tell me a short story about a robot"} ], "max_tokens": 200, "stream": True, "temperature": 0.7, "top_p": 0.95, "top_k": 40, "logprobs": True, "top_logprobs": 2, "stream_options": {"include_usage": True} } invoke_nova_endpoint(streaming_request)

Exemplo 4: conclusão do chat multimodal

Use o formato multimodal para entradas de imagem e texto:

# Multimodal chat request (if supported by your model) multimodal_request = { "messages": [ { "role": "user", "content": [ {"type": "text", "text": "What's in this image?"}, {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}} ] } ], "max_tokens": 150, "temperature": 0.3, "top_p": 0.8, "stream": False } response = invoke_nova_endpoint(multimodal_request)

Etapa 7: limpar os recursos (opcional)

Para evitar cobranças desnecessárias, exclua os recursos da AWS que você criou durante este tutorial. Os endpoints do SageMaker são cobrados enquanto estão em execução, mesmo que você não esteja ativamente fazendo solicitações de inferência.

Importante

A exclusão de recursos é permanente e não poderá ser desfeita. Antes de continuar, certifique-se de que não precisará mais desses recursos.

Excluir o endpoint

import boto3 # Initialize SageMaker client sagemaker = boto3.client('sagemaker', region_name=REGION) try: print("Deleting endpoint...") sagemaker.delete_endpoint(EndpointName=ENDPOINT_NAME) print(f"✅ Endpoint '{ENDPOINT_NAME}' deletion initiated") print("Charges will stop once deletion completes (typically 2-5 minutes)") except Exception as e: print(f"❌ Error deleting endpoint: {e}")
nota

A exclusão do endpoint é assíncrona. Você pode monitorar o status da exclusão:

import time print("Monitoring endpoint deletion...") while True: try: response = sagemaker.describe_endpoint(EndpointName=ENDPOINT_NAME) status = response['EndpointStatus'] print(f"Status: {status}") time.sleep(10) except sagemaker.exceptions.ClientError as e: if e.response['Error']['Code'] == 'ValidationException': print("✅ Endpoint successfully deleted") break else: print(f"Error: {e}") break

Excluir a configuração do endpoint

Depois que o endpoint for excluído, remova a configuração dele:

try: print("Deleting endpoint configuration...") sagemaker.delete_endpoint_config(EndpointConfigName=ENDPOINT_CONFIG_NAME) print(f"✅ Endpoint configuration '{ENDPOINT_CONFIG_NAME}' deleted") except Exception as e: print(f"❌ Error deleting endpoint configuration: {e}")

Excluir o modelo

Remova o objeto do modelo do SageMaker:

try: print("Deleting model...") sagemaker.delete_model(ModelName=MODEL_NAME) print(f"✅ Model '{MODEL_NAME}' deleted") except Exception as e: print(f"❌ Error deleting model: {e}")