Integrando LLMs Customizados com Strands Agents
Organizações que adotam modelos de linguagem de grande escala (LLMs) cada vez mais optam por implantá-los em endpoints de tempo real do SageMaker AI, utilizando frameworks de serving de sua preferência como SGLang, vLLM ou TorchServe. Essa abordagem oferece controle mais granular sobre as implantações, oportunidades de otimização de custos e alinhamento com requisitos de conformidade regulatória.
Contudo, essa flexibilidade introduz um desafio técnico significativo: esses frameworks de serving customizados tipicamente retornam respostas em formatos compatíveis com OpenAI para facilitar suporte amplo em diferentes ambientes. Já o Strands Agents, por sua vez, espera que as respostas dos modelos estejam alinhadas com o formato da Bedrock Messages API. Essa desconexão entre o formato de saída do servidor de modelos e o que Strands espera criar uma lacuna que impede integração perfeita, mesmo quando ambos os sistemas estão tecnicamente funcionais.
Desde dezembro de 2025, o mecanismo de inferência distribuído Amazon Bedrock Mantle passou a oferecer suporte a formatos de mensagem compatíveis com OpenAI. No entanto, essa compatibilidade não é garantida para todos os modelos hospedados em endpoints de tempo real do SageMaker AI. A plataforma permite hospedar diversos modelos que podem exigir formatos específicos de prompt e resposta que não se conformam aos padrões de API conhecidos.
A Solução: Parsers Personalizados
O caminho para resolver essa incompatibilidade é implementar parsers de modelos personalizados que estendem a classe SageMakerAIModel e traduzem o formato de resposta do servidor de modelos para o que Strands espera. Dessa forma, organizações conseguem aproveitar seus frameworks de serving preferidos sem sacrificar a compatibilidade com o SDK do Strands Agents.
Este artigo demonstra como construir parsers personalizados para Strands agents ao trabalhar com LLMs hospedados no SageMaker que não suportam nativamente o formato da Bedrock Messages API. O exemplo prático envolve implantar Llama 3.1 com SGLang no SageMaker utilizando o gerador de projetos de containers ml-container-creator, seguido pela implementação de um parser customizado para integração com Strands agents.
Por que o Erro Ocorre
Quando você implanta modelos usando frameworks de serving customizados como SGLang, vLLM ou TorchServe, eles retornam respostas em seus próprios formatos, frequentemente compatíveis com OpenAI para suporte amplo. Sem um parser personalizado, você encontrará erros como:
TypeError: 'NoneType' object is not subscriptable
Isso acontece porque a classe padrão SageMakerAIModel do Strands Agents tenta fazer parsing de respostas assumindo uma estrutura específica que seu endpoint customizado não fornece.
Arquitetura da Implementação
A implementação se organiza em três camadas distintas:
- Camada de Implantação de Modelo: Llama 3.1 sendo servido pelo SGLang no SageMaker, retornando respostas compatíveis com OpenAI
- Camada de Parser: Classe LlamaModelProvider personalizada que estende SageMakerAIModel para processar o formato de resposta do Llama 3.1
- Camada de Agent: Agent Strands que utiliza o provider customizado para IA conversacional, fazendo o parsing apropriado da resposta do modelo
Preparando o Ambiente
Instalando ml-container-creator
O primeiro passo envolve construir o container de serving para o modelo. Utiliza-se um projeto open-source que automatiza a criação do container e gera scripts de deployment. O ml-container-creator, um gerador Yeoman de código aberto mantido pela AWS Labs, automatiza a criação de projetos de deployment BYOC (Bring Your Own Container) para o SageMaker. Ele gera os artefatos necessários para construir containers de serving de LLM, incluindo Dockerfiles, configurações CodeBuild e scripts de deployment.
Para instalar, execute:
# Instalar Yeoman globalmente
npm install -g yo
# Clonar e instalar ml-container-creator
git clone https://github.com/awslabs/ml-container-creator
cd ml-container-creator
npm install && npm link
# Verificar instalação
yo --generators
# Deve mostrar ml-container-creator
Gerando o Projeto de Deployment
Uma vez instalado e vinculado, o comando yo ml-container-creator permite executar o gerador necessário para este exercício:
# Executar o gerador
yo ml-container-creator
# Opções de configuração:
# - Framework: transformers
# - Model Server: sglang
# - Model: meta-llama/Llama-3.1-8B-Instruct
# - Deploy Target: codebuild
# - Instance Type: ml.g6.12xlarge (GPU)
# - Region: us-east-1
O gerador cria uma estrutura de projeto completa:
<project-directory>/
├── Dockerfile # Container com SGLang e dependências
├── buildspec.yml # Configuração CodeBuild
├── code/
│ └── serve # Script de inicialização do servidor SGLang
├── deploy/
│ ├── submit_build.sh # Dispara CodeBuild
│ └── deploy.sh # Faz deploy no SageMaker
└── test/
└── test_endpoint.sh # Script de teste do endpoint
Compilação e Implantação
Projetos criados pelo ml-container-creator incluem scripts de build e deployment templatizados. Os scripts ./deploy/submit_build.sh e ./deploy/deploy.sh são utilizados para construir a imagem, fazer push para Amazon Elastic Container Registry (ECR) e fazer deploy em um endpoint de tempo real do SageMaker AI.
cd llama-31-deployment
# Compilar container com CodeBuild (Docker local não necessário)
./deploy/submit_build.sh
# Fazer deploy no SageMaker
./deploy/deploy.sh arn:aws:iam::ACCOUNT:role/SageMakerExecutionRole
O processo de implantação funciona da seguinte forma:
- CodeBuild constrói a imagem Docker com SGLang e Llama 3.1
- A imagem é enviada para Amazon ECR
- SageMaker cria um endpoint de tempo real
- SGLang baixa o modelo do HuggingFace e o carrega na memória da GPU
- O endpoint atinge o status InService (aproximadamente 10-15 minutos)
Você pode testar o endpoint usando ./test/test_endpoint.sh ou com uma invocação direta:
import boto3
import json
runtime_client = boto3.client('sagemaker-runtime', region_name='us-east-1')
payload = {
"messages": [
{"role": "user", "content": "Hello, how are you?"}
],
"max_tokens": 100,
"temperature": 0.7
}
response = runtime_client.invoke_endpoint(
EndpointName='llama-31-deployment-endpoint',
ContentType='application/json',
Body=json.dumps(payload)
)
result = json.loads(response['Body'].read().decode('utf-8'))
print(result['choices'][0]['message']['content'])
Compreendendo o Formato de Resposta
O Llama 3.1 retorna respostas compatíveis com o padrão OpenAI. O Strands, por sua vez, espera que as respostas dos modelos sigam o formato da Bedrock Messages API. Até recentemente, essa era uma desconexão padrão de compatibilidade. Desde dezembro de 2025, o mecanismo de inferência distribuído Amazon Bedrock Mantle passou a oferecer suporte a formatos de mensagem OpenAI.
Uma resposta típica do Llama 3.1 em formato OpenAI se parece assim:
{
"id": "cmpl-abc123",
"object": "chat.completion",
"created": 1704067200,
"model": "meta-llama/Llama-3.1-8B-Instruct",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "I'm doing well, thank you for asking!"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 23,
"completion_tokens": 12,
"total_tokens": 35
}
}
Porém, o suporte ao formato da Messages API não é garantido para todos os modelos hospedados em endpoints de tempo real do SageMaker AI. A plataforma permite hospedar diversos tipos de modelos base em infraestrutura gerenciada com aceleração por GPU, alguns dos quais podem exigir formatos esotéricos de prompt e resposta que não se conformam a APIs padrão. Por exemplo, a classe SageMakerAIModel padrão utiliza o formato legado da Bedrock Messages API e tenta acessar campos que não existem no formato OpenAI Messages padrão, causando falhas do tipo TypeError.
Implementando um Parser Personalizado
Parsers de modelos personalizados são um recurso da SDK do Strands Agents que oferece forte compatibilidade e flexibilidade para clientes que constroem agents alimentados por LLMs hospedados no SageMaker AI. A criação de um provider customizado que estende SageMakerAIModel segue este padrão:
def stream(self, messages: List[Dict[str, Any]], tool_specs: list, system_prompt: Optional[str], **kwargs):
# Construir mensagens de payload
payload_messages = []
if system_prompt:
payload_messages.append({"role": "system", "content": system_prompt})
# Extrair conteúdo de mensagem do formato Strands
for msg in messages:
payload_messages.append({"role": "user", "content": msg['content'][0]['text']})
# Construir payload completo com streaming habilitado
payload = {
"messages": payload_messages,
"max_tokens": kwargs.get('max_tokens', self.max_tokens),
"temperature": kwargs.get('temperature', self.temperature),
"top_p": kwargs.get('top_p', self.top_p),
"stream": True
}
try:
# Invocar endpoint SageMaker com streaming
response = self.runtime_client.invoke_endpoint_with_response_stream(
EndpointName=self.endpoint_name,
ContentType='application/json',
Accept='application/json',
Body=json.dumps(payload)
)
# Processar resposta de streaming
accumulated_content = ""
for event in response['Body']:
chunk = event['PayloadPart']['Bytes'].decode('utf-8')
if not chunk.strip():
continue
# Parse de formato SSE: "data: {json}\n"
for line in chunk.split('\n'):
if line.startswith('data: '):
try:
json_str = line.replace('data: ', '').strip()
if not json_str:
continue
chunk_data = json.loads(json_str)
if 'choices' in chunk_data and chunk_data['choices']:
delta = chunk_data['choices'][0].get('delta', {})
# Renderizar delta de conteúdo em formato Strands
if 'content' in delta:
content_chunk = delta['content']
accumulated_content += content_chunk
yield {
"type": "contentBlockDelta",
"delta": {"text": content_chunk},
"contentBlockIndex": 0
}
# Verificar conclusão
finish_reason = chunk_data['choices'][0].get('finish_reason')
if finish_reason:
yield {
"type": "messageStop",
"stopReason": finish_reason
}
# Renderizar metadados de uso
if 'usage' in chunk_data:
yield {
"type": "metadata",
"usage": chunk_data['usage']
}
except json.JSONDecodeError:
continue
except Exception as e:
yield {
"type": "error",
"error": {
"message": f"Endpoint invocation failed: {str(e)}",
"type": "EndpointInvocationError"
}
}
O método stream substitui o comportamento padrão de SageMakerAIModel e permite que o agent faça parsing de respostas conforme os requisitos do modelo subjacente. Embora a maioria dos modelos suporte o protocolo OpenAI Messages API, esse recurso permite que usuários avançados aproveitem LLMs altamente especializados no SageMaker AI para alimentar workloads de agents utilizando a SDK do Strands Agents.
Inicializando Agents com Providers Personalizados
Uma vez que a lógica personalizada de resposta do modelo é construída, a SDK do Strands Agents simplifica a inicialização de agents com providers de modelos customizados:
from strands.agent import Agent
# Inicializar provider customizado
provider = LlamaModelProvider(
endpoint_name="llama-31-deployment-endpoint",
region_name="us-east-1",
max_tokens=1000,
temperature=0.7
)
# Criar agent com provider customizado
agent = Agent(
name="llama-assistant",
model=provider,
system_prompt=(
"You are a helpful AI assistant powered by Llama 3.1, "
"deployed on Amazon SageMaker. You provide clear, accurate, "
"and friendly responses to user questions."
)
)
# Testar o agent
response = agent("What are the key benefits of deploying LLMs on SageMaker?")
print(response.content)
Recursos Disponíveis
A implementação completa deste parser customizado, incluindo um notebook Jupyter com explicações detalhadas e o projeto de deployment gerado pelo ml-container-creator, está disponível no repositório GitHub acompanhador.
Considerações Finais
Construir parsers de modelos personalizados para Strands agents oferece aos usuários a oportunidade de aproveitar diferentes deployments de LLM no SageMaker, independentemente do formato de resposta que o modelo retorna. Ao estender SageMakerAIModel e implementar o método stream(), é possível integrar modelos hospedados customizadamente enquanto se mantém a interface limpa do agent oferecida pelo Strands.
Os principais aprendizados deste artigo são:
- O ml-container-creator simplifica deployments SageMaker BYOC com código de infraestrutura pronto para produção
- Parsers personalizados preenchem a lacuna entre os formatos de resposta do servidor de modelos e as expectativas do Strands
- O método
stream()é o ponto crítico de integração para providers customizados
Fonte
Building custom model provider for Strands Agents with LLMs hosted on SageMaker AI endpoints (https://aws.amazon.com/blogs/machine-learning/building-custom-model-provider-for-strands-agents-with-llms-hosted-on-sagemaker-ai-endpoints/)
Leave a Reply