Instrumentar um framework de GenAI para o OpenTelemetry significa localizar pontos de extensão aproveitáveis, mapear suas operações para as convenções semânticas, verificar a telemetria gerada e manter tudo isso funcionando conforme o framework evolui. Como cada framework novo repete o ciclo, o custo de engenharia se acumula: o artigo observa que até um colaborador experiente pode gastar dias em um único framework, e que implementações independentes costumam divergir na escolha de atributos, unidades e fronteiras de spans, o que dificulta consultar a telemetria de forma uniforme.

Os autores vêm avaliando se uma equipe de agentes consegue assumir boa parte desse trabalho. No sistema deles, os agentes examinam um framework, propõem um plano de instrumentação, o implementam conforme as Semantic Conventions for GenAI do OpenTelemetry e verificam a telemetria emitida com o OpenTelemetry Weaver, enquanto engenheiros revisam o plano e o pull request resultante.

Os componentes

Três peças compõem o sistema: as convenções semânticas de GenAI, o Weaver e um orquestrador.

  • As convenções semânticas de GenAI definem o contrato: nomes de spans e atributos, unidades e quais campos carregam o modelo, os tokens, as mensagens e as chamadas de ferramentas. Os plugins gerados devem seguir essas definições.
  • O Weaver é o validador. Sua capacidade live-check abre um listener OTLP, passa a telemetria de uma instrumentação em execução por um conjunto de advisors e a compara com o registro resolvido de convenções semânticas. Advisors internos cobrem o básico (missing_attribute, type_mismatch); as políticas Rego padrão do OTel acrescentam regras de nomenclatura e formato, e políticas próprias podem ser fornecidas com --advice-policies. Cada achado tem um nível — violation, improvement ou information — e o Weaver sai com código diferente de zero quando o relatório contém uma violação.
  • O orquestrador agenda os agentes e publica o pull request resultante. A equipe usa uma plataforma de tarefas multiagente auto-hospedada, mas observa que um orquestrador só precisa ser capaz de clonar um repositório, executar os agentes e abrir um pull request.

Arquitetura

Diante do repositório de um framework — LangChain é o exemplo dado — o sistema produz um pull request com a instrumentação gerada.

flowchart LR
    repo[("Target framework<br/>repository")] --> team
    subgraph team["Agent team"]
        direction TB
        lead["Team Lead"]
        research["Research"]
        plan["Plan Review"]
        code["Coding"]
        review["Code Review<br/>& Tests"]
        e2e["E2E & Deploy"]
        observe["Observation"]
        lead --- research
        lead --- plan
        lead --- code
        lead --- review
        lead --- e2e
        lead --- observe
    end
    team --> pr["Pull request with<br/>generated instrumentation"]
    semconv["GenAI semantic<br/>conventions"] -.->|"target spec"| team
    weaver["Weaver<br/>live-check"] -.->|"conformance signal"| team

O orquestrador distribui o trabalho entre agentes especializados:

  • Team Lead Agent: inicia a execução, ordena o trabalho dos demais agentes e detém o estado do loop.
  • Instrumentation Research Agent: clona o framework alvo, estuda sua arquitetura e procura primeiro pontos de extensão públicos, como hooks e callbacks. Após levantar implementações existentes, produz um relatório de pesquisa com estratégias de instrumentação candidatas, considerando o wrapping de métodos apenas quando esses pontos de extensão não conseguem observar o ciclo de vida necessário.
  • Plan Review Agent: pontua as candidatas em complexidade, manutenibilidade e cobertura, escolhe a melhor e escreve um execution-plan.md detalhado indicando quais pontos de extensão usar e de onde cada atributo deve vir. Os autores o descrevem como essencialmente uma etapa de geração de especificação e o ponto natural para revisão humana.
  • Coding Agent: implementa o execution-plan.md segundo as convenções semânticas de GenAI, usando os utilitários GenAI compartilhados.
  • Code Review & Test Validation Agent: revisa o código gerado, escreve testes unitários e itera com o Coding Agent até que os comentários sejam resolvidos e os testes passem.
  • E2E Generation & Deployment Agent: atua em paralelo com a codificação, preparando o harness de testes de integração para que esteja pronto quando o plugin estiver.
  • Observation Agent: executa o harness no Kubernetes, captura a saída OTLP, a entrega ao Weaver para validação semântica e acompanha a carga de trabalho ao longo do tempo em busca de vazamentos de memória e outras regressões em execução.

Engenharia do loop

O Team Lead Agent agenda cada etapa e os demais agentes devolvem o controle a ele quando o plano precisa mudar.

flowchart TB
    research["1. Instrumentation research<br/><i>clone, study, propose strategies</i>"]
    plan["2. Plan review & scoring<br/><i>produces execution-plan.md</i>"]
    code["3. Coding<br/><i>implements against semconv</i>"]
    tests["4. Code review & unit tests"]
    e2e["5. E2E integration test<br/>on Kubernetes"]
    check["6. Weaver live-check<br/><i>semantic validation</i>"]
    soak["7. Soak & runtime watch<br/><i>memory, leaks, regressions</i>"]
    pr["Pull request"]
    research --> plan --> code --> tests --> e2e --> check
    check -->|"violation"| code
    check -->|"clean"| soak
    soak -->|"regression"| code
    soak -->|"stable"| pr

Depois da revisão de código e dos testes unitários, os exemplos do framework são empacotados em imagens de contêiner, instrumentados e executados em um cluster Kubernetes de teste, enquanto o Weaver inspeciona os dados OTLP emitidos. Uma violação devolve o trabalho ao Coding Agent; uma execução limpa avança para um soak test que observa vazamentos de memória e outras regressões. Um engenheiro pode interromper o Team Lead para alterar o plano ou repetir uma etapa.

Como é a instrumentação gerada

Duas decisões moldam o código gerado.

A primeira é que o execution-plan.md fixa os pontos de instrumentação antes de qualquer código ser escrito, e o Coding Agent não recebe ordem de envolver métodos: a etapa de pesquisa prefere hooks e callbacks públicos quando eles cobrem o ciclo de vida exigido. O OpenAI Agents SDK, por exemplo, expõe lifecycle hooks, então essa estratégia tem prioridade nele. O plugin do CrewAI apresentado no artigo suporta versões até a 0.80, cujos callbacks públicos não expunham o ciclo completo de início e fim necessário a esses spans, então seu plano escolheu o wrapping de métodos; os autores observam que versões mais novas do CrewAI trazem uma API de event listeners mais ampla, que deve ser avaliada primeiro para essas versões. O plano de wrapping aparece como uma lista explícita e revisável de alvos:

_CREWAI_UNINSTRUMENT_TARGETS = (
    ("crewai.crew", "Crew", "kickoff"),
    ("crewai.crew", "Crew", "kickoff_async"),
    ("crewai.flow.flow", "Flow", "kickoff"),
    ("crewai.flow.flow", "Flow", "kickoff_async"),
    ("crewai.agent", "Agent", "execute_task"),
    ("crewai.task", "Task", "execute_sync"),
    ("crewai.tools.tool_usage", "ToolUsage", "_use"),
)

Revisores podem conferir esses alvos diretamente com a API do framework.

A segunda é que os agentes mapeiam os objetos do framework para utilitários GenAI compartilhados em vez de escrever eles mesmos os nomes de atributos. O opentelemetry-util-genai agora vive no repositório OpenTelemetry Python GenAI, em util/opentelemetry-util-genai, e seu método handler.invoke_local_agent() cria um AgentInvocation para um agente que roda no mesmo processo.

Um adaptador mínimo para o exemplo do CrewAI — escrito contra a API upstream, e não um trecho do plugin gerado original — fica assim, onde wrapped é o método Agent.execute_task original já vinculado, instance é o agente do CrewAI e args e kwargs são os argumentos do método (o registro do wrapper e a configuração do SDK foram omitidos):

from opentelemetry.util.genai.handler import get_telemetry_handler
def wrap_execute_task(wrapped, instance, args, kwargs):
    handler = get_telemetry_handler()
    with handler.invoke_local_agent(agent_name=instance.role) as invocation:
        if invocation.should_capture_content:
            invocation.agent_description = instance.goal
        return wrapped(*args, **kwargs)

O utilitário abre um span INTERNAL, o torna atual durante a chamada e o encerra quando o gerenciador de contexto sai. Se o método envolvido lança uma exceção, o utilitário registra a falha e deixa a exceção propagar. O wrapper verifica should_capture_content antes de copiar o objetivo do agente, que pode conter instruções fornecidas pelo usuário.

Os utilitários compartilhados cuidam dos nomes de atributos e do ciclo de vida da telemetria, mas a instrumentação ainda precisa mapear corretamente os dados do framework; a equipe valida esses mapeamentos com testes e revisão humana, além das verificações de registro do Weaver.

Os plugins gerados originalmente usavam extensões da equipe sobre um pacote de utilitários anterior em alibaba/loongsuite-python, incluindo o tipo InvokeAgentInvocation específico do fork. O exemplo acima usa a API upstream para que os leitores possam começar com os utilitários do OpenTelemetry, e a migração dos plugins para essas APIs faz parte do trabalho de upstreaming descrito adiante.

Verificando o resultado com o Weaver

O Weaver compara os dados OTLP emitidos com o registro resolvido de convenções semânticas, mas o Observation Agent não o chama diretamente. Ele conduz a execução por meio de um conformance runner, que inicia o cenário, o aponta para um endpoint OTLP e entrega o que ele emite ao weaver registry live-check. Um exemplo público, executado a partir da raiz do repositório semantic-conventions-conformance, é:

otel-conformance scenarios/gen-ai/python/crewai/openinference

Seu conformance.yaml declara o runner e a instrumentação:

runner: genai-conformance
instrumented_library: crewai
instrumentation_library: openinference-instrumentation-crewai

O artigo ressalta que esse cenário público demonstra o runner com o OpenInference, e não com o plugin da LoongSuite da equipe, e que o wrapper genai-conformance fornece o pin do registro de convenções semânticas e as políticas de advice, de modo que execuções repetidas usem as mesmas definições.

Cada cenário roda então sob seu próprio live-check, e uma violação faz a execução falhar. Esse é o padrão; --report-only é a exceção, que rebaixa os achados semânticos a avisos mas continua reprovando um cenário que travou ou não produziu nada mensurável.

Em uma execução separada com o plugin da LoongSuite da equipe, uma entidade de exemplo retornou este achado:

{
  "live_check_result": {
    "all_advice": [
      {
        "id": "span_status_ok_set_by_instrumentation",
        "context": { "status_code": "ok" },
        "message": "Span 'execute_tool ping' has status.code='ok'; instrumentations must leave status UNSET on success (OK is reserved for application code).",
        "level": "violation",
        "signal_type": "span",
        "signal_name": "execute_tool ping"
      }
    ],
    "highest_advice_level": "violation"
  }
}

A política de advice agnóstica de domínio do runner sinaliza uma instrumentação que define o status do span como OK — o Coding Agent havia feito isso em um span de ferramenta — e o código de saída diferente de zero do Weaver devolveu o achado para correção.

O mesmo runner também produz uma matriz de cobertura que mostra quais atributos definidos pelo registro um plugin emite e quais ele deixa de fora. Cobertura é medição, não bloqueio, por isso essas execuções usam --report-only: um atributo ausente é registrado, não uma quebra de build. A matriz mostra cobertura de 29 atributos em 14 instrumentações Python da LoongSuite, e a live conformance matrix da comunidade apresenta o mesmo sinal para os cenários públicos, embora atualmente não inclua os plugins da LoongSuite.

Separadamente, o Weaver live-check reporta como violações missing_attribute os atributos que um plugin emite e que o registro não define. Quando a equipe aceita temporariamente uma violação conhecida, registra seu id e o motivo em expected_violations; a execução também falha se o Weaver para de reportar essa violação, indicando que a entrada está obsoleta e deve ser removida. A cobertura conta apenas atributos definidos pelo registro, então extensões próprias não elevam a pontuação de um plugin, e quando vários frameworks precisam do mesmo atributo que o registro não define, a equipe propõe adicioná-lo às convenções semânticas de GenAI.

O Coding Agent havia adicionado atributos gen_ai.crewai.* ao plugin do CrewAI para detalhes ausentes das convenções semânticas de GenAI; como a orientação de nomenclatura do OpenTelemetry alerta que esse prefixo pode colidir com futuros atributos padrão, os autores afirmam que os próximos prompts deveriam exigir que os agentes emitam apenas atributos de GenAI definidos pelo registro.

O que já foi entregue

O sistema conduziu o desenvolvimento de instrumentação de ponta a ponta para uma variedade de frameworks de GenAI, SDKs de agentes e benchmarks de agentes. Os plugins gerados estão nos pacotes instrumentation-loongsuite do Loongsuite Python, a distribuição Apache-2.0 da Alibaba da instrumentação Python do OpenTelemetry, e cada um foi revisado por uma pessoa na etapa de pull request. Atualmente eles cobrem frameworks e SDKs como CrewAI, LangChain, LangGraph, LiteLLM, AutoGen, AgentScope, Dify, Google ADK, o Claude Agent SDK, o Microsoft Agent Framework, MCP e mem0, além de benchmarks de agentes como BFCL-v4, WebArena e MiniSWEAgent. As verificações de conformidade ajudam a manter a telemetria consistente entre frameworks.

A equipe também trabalha para levar essa instrumentação ao projeto OpenTelemetry Python GenAI; a discussão está aberta em opentelemetry-python-genai#185, e os primeiros pull requests de instrumentação já estão em revisão.

O que não funcionou

A primeira versão não tinha a verificação do Weaver: um modelo revisor julgava se a telemetria seguia a especificação, mas às vezes aprovava saídas que violavam as convenções. A equipe substituiu esse julgamento por uma comparação com o registro e um código de saída diferente de zero em caso de violações.

O Claude Agent SDK impôs um limite diferente: seu pacote Python envolve a CLI de código fechado Claude Code, onde acontecem o loop do agente, o despacho de ferramentas e as chamadas ao modelo. O plugin consegue relatar o que cruza a fronteira do processo, mas não captura detalhes dentro da CLI, e pesquisa e planejamento não recuperam dados que o framework não expõe.

O planejamento também continua exigindo conhecimento do framework: o primeiro execution-plan.md costuma deixar passar detalhes do ciclo de vida do framework, então um engenheiro que o conhece revisa o plano antes da implementação. Segundo os autores, os agentes são mais confiáveis implementando um plano revisado do que escolhendo sozinhos a estratégia de instrumentação.

Onde a revisão humana importa

As convenções semânticas fixam nomes de atributos e tipos de span, mas mapear as operações de um framework para elas ainda exige julgamento. Alguns valores têm origem direta: gen_ai.tool.name e gen_ai.tool.call.id vêm do objeto de chamada de ferramenta, enquanto gen_ai.usage.input_tokens e gen_ai.usage.output_tokens vêm dos dados de uso do provedor. Outros mapeamentos pedem mais critério: o CrewAI expõe crews, tasks, agents e flows, e a instrumentação precisa decidir quais de suas operações representam uma operação invoke_agent e quais precisam de um span diferente, uma decisão que requer revisão humana.

O artigo termina com três convites a mantenedores e colaboradores: abrir uma issue no repositório das convenções semânticas de GenAI ao encontrar uma lacuna que os agentes possam enfrentar, participar das discussões do SIG de GenAI Semantic Conventions and Instrumentation para ajudar a moldar o que será padronizado em seguida, e contribuir com o OpenTelemetry Python GenAI conforme as instrumentações geradas avançam para o upstream.