Consumindo Artefatos entre Fluxos no Metaflow: Guia playlist-redux

No desenvolvimento de pipelines de Machine Learning (MLOps), é comum que diferentes etapas do projeto sejam isoladas em fluxos de trabalho distintos. O Metaflow facilita essa modularização permitindo que um fluxo acesse os resultados (artefatos de dados) gerados por execuções anteriores de outros fluxos. Este guia explora como utilizar o Metaflow Client para estabelecer essa dependência de dados de forma eficiente.

O Conceito de Reutilização de Dados

O objetivo central aqui é o desacoplamento. Em vez de um único fluxo gigante que faz tudo, dividimos a lógica: um fluxo processa estatísticas pesadas e outro consome esses resultados para gerar recomandações. No exemplo prático playlist-redux, o fluxo de recomendação não recalcula as estatísticas de bilheteria; ele simplesmente consulta a última execução bem-sucedida do fluxo de análise de dados.

Pré-requisito: O Fluxo de Origem

Para que o fluxo atual funcione, é necessário que o fluxo prodtuor de dados (neste caso, o MovieStatsFlow) tenha sido executado com sucesso pelo menos uma vez. Esse fluxo gera um dicionário chamado genre_stats, contendo quartis de faturamento e dados processados por gênero cinematográfico.

# Comando para preparar os dados de origem
python stats.py run

Implementação: Acessando Dados de Outro Fluxo

Abaixo, apresentamos a estrutura do PlayListFlow, focando em como ele utiliza a API do Metaflow Client para caputrar dados externos.

from metaflow import Flow, Step, Parameter, get_metadata
import random

class FluxoRecomendacao(FlowSpec):
    # Parâmetros de entrada para customizar a execução
    categoria = Parameter("categoria", default="Sci-Fi", help="Gênero para filtrar")
    qtd_filmes = Parameter("quantidade", default=5, help="Número de sugestões")

    @step
    def start(self):
        # Verifica o provedor de metadados atual (local ou remoto)
        print(f"Metadata Provider: {get_metadata()}")

        # Localiza a execução mais recente com sucesso do fluxo de estatísticas
        nome_fluxo_origem = "MovieStatsFlow"
        ultima_execucao = Flow(nome_fluxo_origem).latest_successful_run
        
        print(f"Importando dados de: {ultima_execucao}")

        # Recupera o artefato de dados 'genre_stats'
        self.dados_estatisticos = ultima_execucao.data.genre_stats

        # Segue para processamento paralelo
        self.next(self.gerar_bonus, self.filtrar_por_bilheteria)

    @step
    def filtrar_por_bilheteria(self):
        """Seleciona apenas filmes no topo da bilheteria (acima do 3º quartil)."""
        genero_alvo = self.categoria.lower()
        
        if genero_alvo in self.dados_estatisticos:
            info = self.dados_estatisticos[genero_alvo]
            tabela = info["dataframe"]
            limiar_sucesso = info["quartiles"][-1] # Top 25%

            self.selecionados = [
                titulo for i, titulo in enumerate(tabela["movie_title"])
                if tabela["gross"][i] >= limiar_sucesso
            ]
            random.shuffle(self.selecionados)
        else:
            self.selecionados = []
            
        self.next(self.join)

    @step
    def gerar_bonus(self):
        """Escolhe um filme aleatório de um gênero diferente."""
        todos_generos = list(self.dados_estatisticos.keys())
        genero_aleatorio = random.choice(todos_generos)
        
        filmes_disponiveis = self.dados_estatisticos[genero_aleatorio]["dataframe"]["movie_title"]
        self.filme_extra = random.choice(filmes_disponiveis)
        
        self.next(self.join)

    @step
    def join(self, inputs):
        """Consolida os resultados dos ramos paralelos."""
        self.playlist_final = inputs.filtrar_por_bilheteria.selecionados
        self.extra = inputs.gerar_bonus.filme_extra
        self.next(self.end)

    @step
    def end(self):
        """Exibe o resultado final."""
        print(f"--- Playlist Recomendada: {self.categoria} ---")
        for i, filme in enumerate(self.playlist_final[:self.qtd_filmes], 1):
            print(f"{i}: {filme}")
        print(f"Dica Extra: {self.extra}")

if __name__ == "__main__":
    FluxoRecomendacao()

Análise das Funcionalidades do Metaflow Client

1. Identificação de Metadados

O método get_metadata() é crucial para depuração. Ele informa se o seu ambiente está consultando o banco de dados local ou um serviço de metadados centralizado (como na AWS). Para que a troca de artefatos funcione, ambos os fluxos devem compartilhar o mesmo provedor de metadados.

2. Localizando a Execução Correta

Diferente de latest_run, a propriedade latest_successful_run garante que você não tente ler dados de um processo que falhou ou que ainda está em execução. Isso cria uma barreira de segurança natural na linhagem de dados (data lineage).

3. Deserialização Automática

Quando acessamos run.data.artefato, o Metaflow recupera o objeto Python original. Não é necessário lidar com conversões manuais de JSON ou carregamento de arquivos CSV. Se o MovieStatsFlow salvou um dicionário complexo ou um DataFrame, o PlayListFlow receberá exatamente esse objeto pronto para uso.

Aplicações Práticas em Produção

Este padrão de design é a base para sistemas robustos de IA. Algumas aplicações incluem:

  • Engenharia de Features: Um fluxo processa as features diariamente, e múltiplos modelos de treinamento consomem esses artefatos.
  • Avaliação de Modelos: Um fluxo de treinamento salva o modelo, e um fluxo de auditoria separado o carrega para testes de viés e performance.
  • Monitoramento: Notebooks de análise utilizam o Client API para buscar resultados de execuções históricas e gerar gráficos de tendência.

Ao utilizar o Metaflow Client para interconectar fluxos, você transforma scripts isolados em uma infraestrutura de dados conectada, versionada e auditável.

Tags: Metaflow MLOps Data Engineering Python Workflow Automation

Publicado em 10-20 18:27