Inteligência artificial, sem ruído.
Agentes de IA8 min

Coloque o Agente Dentro do Workflow: O Padrão Híbrido que Une o Melhor dos Dois Mundos

Por que escolher entre workflow e agente se você pode usar os dois? Um tutorial prático do padrão híbrido com ajuste de hiperparâmetros assistido por LLM, usando OpenAI Agents SDK e scikit-learn.

Coloque o Agente Dentro do Workflow: O Padrão Híbrido que Une o Melhor dos Dois Mundos

Por que escolher entre workflow e agente se você pode usar os dois?

Quando construímos uma aplicação com LLMs, uma das primeiras decisões de design é sobre a arquitetura de execução. O paradigma de workflow (fluxo de trabalho) segue uma sequência predefinida: cada etapa é conhecida, a ordem é fixa e temos controle total sobre como a informação flui. Já o paradigma de agente começa com um objetivo e decide quais ações tomar ao longo do caminho — mais flexível, porém com menos previsibilidade.

Mas por que escolher um modelo único para a aplicação inteira? Na prática, muitos sistemas contêm os dois tipos de trabalho. Algumas etapas realizam transformações conhecidas (workflow é ideal), enquanto outras precisam se adaptar com base em resultados intermediários (agente é a escolha natural).

Neste artigo, exploramos o padrão híbrido workflow-agente — uma abordagem que combina a clareza do fluxo predefinido com a flexibilidade da experimentação autônoma — através de um estudo de caso completo: ajuste de hiperparâmetros assistido por LLM.

1. Estudo de caso: ajuste de hiperparâmetros com LLM

O objetivo é construir uma aplicação que recebe um dataset rotulado e uma solicitação em linguagem natural, executa experimentos de classificação e recomenda a melhor configuração encontrada. Naturalmente, dividimos o trabalho em três estágios:

1.1 Preparação do experimento

Traduz a solicitação de modelagem em um briefing com objetivo, métrica de avaliação e configuração de validação cruzada. Como a entrada, a operação desejada e a saída esperada são conhecidas, uma única chamada ao LLM com saída estruturada resolve este estágio.

1.2 Ajuste de hiperparâmetros (o agente)

Aqui está o coração da aplicação. Sabemos o objetivo — encontrar o melhor classificador e seus hiperparâmetros — mas não sabemos a sequência exata de experimentos para chegar lá. O próximo teste útil depende dos resultados observados até o momento. Este estágio é delegado a um agente que escolhe dinamicamente configurações de classificadores, avalia cada uma com validação cruzada e continua explorando.

1.3 Sumarização e relatório

Com o briefing, o histórico de experimentos e a recomendação já disponíveis, transformar tudo em um relatório estruturado é uma operação conhecida. Uma única chamada ao LLM é suficiente novamente.

O resultado é um fluxo claro: a sequência geral é fixa (preparação → exploração → relatório), mas introduzimos autonomia apenas no estágio intermediário, onde o caminho não pode ser determinado antecipadamente.

2. Construindo o workflow de três estágios

A aplicação completa pode ser expressa em três chamadas:

experiment_spec = prepare_experiment(modeling_request, dataset_summary)

recommendation, trial_history = await explore_configurations(
    experiment_spec, dataset_summary
)

report = summarize_run(experiment_spec, trial_history, recommendation)

2.1 Preparando o experimento com saída estruturada

O primeiro estágio converte a solicitação de modelagem e o resumo do dataset em um briefing compacto. Usamos structured output (saída estruturada) do LLM — um recurso que garante que a resposta siga um formato predefinido, simplificando o consumo downstream.

Definimos o schema de saída com Pydantic:

from pydantic import BaseModel, Field

class ExperimentSpec(BaseModel):
    objective: str
    primary_metric: str = Field(
        description="A valid scikit-learn scoring name"
    )
    cv_folds: int

Em seguida, a instrução do LLM e a função que monta o prompt:

PREPARER_INSTRUCTION = (
    "Create an experiment brief from the modeling request "
    "and dataset summary."
)

def build_preparer_prompt(request: str, dataset_summary: dict) -> str:
    return f"Modeling request:\n{request}\n\n"
           f"Dataset summary:\n{json.dumps(dataset_summary, indent=2)}"

E a chamada ao LLM via API Responses da OpenAI:

from openai import AzureOpenAI

llm_client = AzureOpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    azure_endpoint=os.environ["OPENAI_API_BASE"],
    api_version=os.environ["OPENAI_API_VERSION"],
)

def prepare_experiment(request: str, dataset_summary: dict) -> ExperimentSpec:
    response = llm_client.responses.parse(
        model="gpt-5.4",
        reasoning={"effort": "medium"},
        instructions=PREPARER_INSTRUCTION,
        input=build_preparer_prompt(request, dataset_summary),
        text_format=ExperimentSpec,
    )
    return response.output_parsed

2.2 Construindo o agente de experimentação

Este é o único estágio autônomo do workflow. O agente precisa decidir quais configurações testar com base nos scores observados. Usamos o SDK openai-agents para orquestrar o loop agente-ferramenta.

Primeiro, configuramos o cliente assíncrono:

from openai import AsyncAzureOpenAI
from agents import OpenAIResponsesModel

agent_client = AsyncAzureOpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    azure_endpoint=os.environ["OPENAI_API_BASE"],
    api_version=os.environ["OPENAI_API_VERSION"],
)

agent_model = OpenAIResponsesModel(
    model="gpt-5.4",
    openai_client=agent_client,
)

Depois, definimos o schema de saída da recomendação:

class AgentRecommendation(BaseModel):
    model_name: str
    hyperparameters_json: str
    rationale: str

AGENT_INSTRUCTION = "Find a strong classifier for the supplied problem."

A ferramenta que o agente usa para executar um experimento é intencionalmente simples — ela apenas avalia a configuração de classificador fornecida pelo agente usando scikit-learn:

from agents import function_tool
from sklearn.model_selection import cross_val_score
from sklearn.utils import all_estimators

def build_experiment_tool(X, y, experiment_spec, trial_history):
    classifiers = dict(all_estimators(type_filter="classifier"))

    @function_tool
    def run_experiment(model_name: str, hyperparameters_json: str) -> str:
        # Evaluate one scikit-learn classifier configuration
        parameters = json.loads(hyperparameters_json)
        classifier = classifiers[model_name](**parameters)

        scores = cross_val_score(
            classifier, X, y,
            cv=experiment_spec.cv_folds,
            scoring=experiment_spec.primary_metric,
        )

        result = {
            "model_name": model_name,
            "hyperparameters": parameters,
            "mean_score": round(float(scores.mean()), 4),
        }

        trial_history.append(result)
        return json.dumps(result)

    return run_experiment

O agente pode escolher qualquer classificador do registro do scikit-learn e fornecer argumentos do construtor como JSON. Para o loop principal, usamos o Runner do SDK:

from agents import Agent, ModelSettings, Runner

async def explore_configurations(X, y, experiment_spec, dataset_summary):
    trial_history = []

    run_experiment = build_experiment_tool(
        X, y, experiment_spec, trial_history
    )

    agent = Agent(
        name="Model selection agent",
        instructions=AGENT_INSTRUCTION,
        model=agent_model,
        model_settings=ModelSettings(
            reasoning={"effort": "medium"},
            parallel_tool_calls=True,  # Permite testar várias configs em paralelo
        ),
        tools=[run_experiment],
        output_type=AgentRecommendation,
    )

    result = await Runner.run(
        agent,
        build_agent_prompt(experiment_spec, dataset_summary),
        max_turns=10,
    )

    return result.final_output, trial_history

Destaque:parallel_tool_calls=True permite que o agente solicite várias configurações simultaneamente, acelerando a exploração.

2.3 Sumarizando a execução

O estágio final transforma a execução completa em um relatório conciso. É uma tarefa fechada — uma única chamada ao LLM resolve:

class ModelSelectionReport(BaseModel):
    selected_model: str
    selected_hyperparameters: str
    mean_cv_score: float
    summary: str

def summarize_run(experiment_spec, trial_history, agent_recommendation):
    response = llm_client.responses.parse(
        model="gpt-5.4",
        reasoning={"effort": "medium"},
        instructions="Summarize the completed model-selection run.",
        input=build_reporter_prompt(
            experiment_spec, trial_history, agent_recommendation
        ),
        text_format=ModelSelectionReport,
    )
    return response.output_parsed

3. Testando com classificação de dígitos manuscritos

Para validar a aplicação, usamos o dataset de dígitos manuscritos do scikit-learn (1.797 amostras, 64 features, 10 classes):

from sklearn.datasets import load_digits

digits = load_digits()
X, y = digits.data, digits.target

dataset_summary = {
    "n_samples": X.shape[0],
    "n_features": X.shape[1],
    "n_classes": len(set(y)),
}

modeling_request = (
    "Build a classifier for handwritten digit images. "
    "Use cross-validation to compare candidate models. "
    "Recommend a strong configuration."
)

Resultado do primeiro estágio — o LLM produziu o seguinte experiment spec:

{
    "objective": "Build and evaluate a classifier for handwritten digit images.",
    "primary_metric": "accuracy",
    "cv_folds": 5
}

Resultado do agente — 19 experimentos foram executados. Entre os 5 primeiros:

[
    {"model": "LogisticRegression", "params": {"max_iter": 1000}, "score": 0.9132},
    {"model": "RandomForestClassifier", "params": {"n_estimators": 100}, "score": 0.9382},
    {"model": "SVC", "params": {"C": 1, "kernel": "rbf"}, "score": 0.9638},
    {"model": "KNeighborsClassifier", "params": {"n_neighbors": 3}, "score": 0.9661},
    {"model": "SVC", "params": {"C": 10, "kernel": "rbf"}, "score": 0.9750}
]

O agente explorou LogisticRegression, RandomForest, SVC, KNeighbors e refinou a melhor configuração: SVC com C=10 e kernel RBF alcançou 97,5% de acurácia em validação cruzada de 5 folds.

Relatório final:

{
    "selected_model": "SVC",
    "selected_hyperparameters": '{"C": 10, "kernel": "rbf", "gamma": "scale"}',
    "mean_cv_score": 0.975,
    "summary": "O SVC com C=10 e kernel RBF obteve o melhor score
                de validação cruzada entre os candidatos testados."
}

4. Quando usar este padrão

Ao construir sua próxima aplicação com LLM, a pergunta não deve ser “toda a aplicação deve ser um workflow ou um agente?”, mas sim:

Onde a aplicação precisa de autonomia?

  • Se um estágio tem uma operação conhecida, use uma chamada estruturada ao LLM (workflow).
  • Se um estágio tem um objetivo claro, mas o próximo passo depende do que o sistema observar, use um agente.
  • Decomponha o caminho da solução em estágios e coloque autonomia apenas onde o caminho precisa ser descoberto.

É assim que você mantém a clareza e o controle de um workflow enquanto aproveita a flexibilidade dos agentes para resolver problemas de forma mais eficaz.

Por que isso importa agora

Com o SDK openai-agents e as APIs de saída estruturada se tornando maduros em 2026, o padrão híbrido workflow-agente deixou de ser um conceito teórico para se tornar uma arquitetura prática e implementável em produção. Empresas que estão construindo pipelines de ML assistidos por IA — desde AutoML até experimentação automatizada — podem adotar este padrão hoje para combinar o melhor dos dois mundos: previsibilidade onde é necessária e adaptabilidade onde é valiosa.


Descubra mais sobre noticiAI

Assine para receber nossas notícias mais recentes por e-mail.

R
Sobre o autorRedação Noticiai

Equipe editorial dedicada a explicar inteligência artificial com clareza, independência e contexto.