A Node.js Integration Case Study

🦜×🔭 Instrumenting a Multi-Provider AI App with Arize Phoenix

For AI engineers instrumenting non-standard LLM pipelines with OpenTelemetry and Arize Phoenix

How to trace a template-driven, multi-provider AI pipeline with OpenTelemetry and Phoenix: from prompt assembly through provider dispatch to structured evaluations. Built against a production app with 1,022 templates across 38 languages and 9 AI providers.

6
Templates Tested
6
Providers Launched
50
Spans Captured
100%
Eval Pass Rate

Harold Moses II · September 2026

🦜 About Poly-Glot AI — The System Under Test

Poly-Glot AI Workspace is a production app I built — a universal AI prompt workspace with 1,022 templates across 38 languages, dispatching to 9 AI providers (ChatGPT, Claude, Gemini, Grok, Perplexity, Copilot, Mistral, DuckDuckGo AI, HuggingChat). Users pick a template, fill smart fields, and send the assembled prompt to one or multiple AIs via Compare Mode.

Why I tested it: This report documents how I instrumented my own non-standard, template-driven, multi-provider AI pipeline with OpenTelemetry and Arize Phoenix — tracing the full lifecycle from prompt assembly through provider dispatch to structured evaluations. The goal: demonstrate production-grade observability for LLM applications that don't follow the typical chat-completion pattern.

Table of Contents

  1. Integration Overview
  2. Application Architecture & Instrumentation
  3. Test Results, Provider Coverage & Evaluations
  4. Phoenix Dashboard: Live Trace Data
  5. Application Test Flow Screenshots
  6. Node.js Setup Guide: Protobuf OTLP + ESM
  7. Span Reference & Production Context
  8. Production MCP Server & Tool Surface

1. Integration Overview

The Pattern

This case study instruments Poly-Glot AI Workspace, a production app with 1,022 prompt templates across 38 languages, with Arize Phoenix observability. The pipeline is template-driven and multi-provider: it assembles prompts from structured templates and dispatches to 9 AI providers (ChatGPT, Claude, Gemini, Grok, Perplexity, Copilot, Mistral, DuckDuckGo AI, HuggingChat). All 6 test cases are automated with Playwright, producing real OpenTelemetry traces with parent/child span hierarchies and structured evaluations flowing into Phoenix.

What Makes This Integration Different

Most Phoenix examples trace direct LLM API calls. This app doesn't make server-side LLM calls at all. It builds prompts and routes them to provider web UIs. Phoenix still captures the full pipeline because the instrumentation targets the application logic, not the model call:

Node.js Setup: Key Finding — Phoenix v20+ only accepts protobuf OTLP and returns HTTP 415 for JSON-encoded traces. Most Node.js OpenTelemetry tutorials default to JSON. The exact package pins and ESM compatibility workarounds that work are documented in Section 6.

2. Application Architecture & Instrumentation

The Application

Poly-Glot AI Workspace is a multilingual prompt platform with 1,022 templates across 38 languages. It ships as a native Mac/iOS app and a remote MCP server (15 tools on 4+ registries).

  • 25 free / 997 Pro templates
  • Compare Mode: same prompt to multiple AIs
  • BYOM: Bring Your Own Model endpoints
  • Dispatches to provider web UIs, not APIs
  • No server-side LLM calls

Why Trace This Pipeline?

The pipeline has 7 stages per interaction. Each is a potential failure point invisible without tracing:

  1. Template navigation and selection
  2. Dynamic field population
  3. Prompt assembly from template + variables
  4. Entitlement/paywall gating
  5. Provider selection and dispatch
  6. Multi-provider Compare Mode
  7. Result evaluation

Instrumentation Architecture

Playwright Test Runner (Node.js ESM)
├─ @opentelemetry/sdk-trace-base 1.30.1 (BasicTracerProvider)
├─ @opentelemetry/exporter-trace-otlp-proto 0.57.2 (protobuf — required by Phoenix)
├─ @opentelemetry/resources 1.30.1
│   └─ Resource: openinference.project.name = "polyglot-free-templates"
▼
Arize Phoenix v20.11.0 (localhost:6006)
├─ OTLP: /v1/traces (protobuf only — HTTP 415 for JSON)
├─ GraphQL: /graphql
└─ Web UI: traces, spans, evaluations

Span Hierarchy (Per Test Case)

polyglot.free_template (ROOT)
├─ template.navigate ~1s
├─ template.open ~1s
├─ template.fill_fields ~5ms
├─ template.build_prompt ~1ms
├─ template.send_attempt ~11s
├─ provider.*.launch ~10.5s
└─ template.evaluate <1ms

Key Span Attributes

AttributeDescription
template.nameDisplay name
template.plan"free"
template.goalCategory
prompt.lengthChar count
provider.keyProvider ID
provider.autofill?q= support
provider.urlURL opened
test.statuspassed/failed

3. Test Results: 6 Free Templates × 6 Providers

#TemplateGoalProviderDeliveryFieldsPromptEvalsStatus
1Code Review AssistantCode & DevChatGPTAuto-fill (?q=)4892ch3/3
2Interview CoachCareerGrokAuto-fill (?q=)5734ch3/3
3Summarization ToolWritingPerplexityClipboard61,203ch3/3
4Study Guide CreatorSchoolDuckDuckGoClipboard5612ch3/3
5LinkedIn Post WriterContentGeminiClipboard5487ch3/3
6Recipe GeneratorCookingClaudeClipboard5395ch3/3
8
Traces
50
Spans
18/18
Evals Passed
0
Defects

Evaluation Details (18/18 Passed)

1. Summarization Tool

  • bullet_count_range
  • content_present
  • conciseness

2. Code Review

  • empty_list_risk
  • markdown_output
  • actionable_rec

3. Interview Coach

  • question_count_5
  • tech_behav_mix
  • role_relevance

4. Study Guide

  • trace_span_eval
  • question_count_5
  • structured_sections

5. LinkedIn Post

  • word_count_150_200
  • professional_tone
  • cta_present

6. Recipe Generator

  • ingredient_list
  • cooking_steps
  • dietary_notes

Provider Coverage (6 of 9)

ProviderDeliveryMethodTemplateStatus
ChatGPTAuto-fill (?q=)URL paramCode Review Assistant
GrokAuto-fill (?q=)URL paramInterview Coach
PerplexityPaste-requiredClipboardSummarization Tool
DuckDuckGoPaste-requiredClipboardStudy Guide Creator
GeminiPaste-requiredClipboardLinkedIn Post Writer
ClaudePaste-requiredClipboardRecipe Generator
Copilot / Mistral / HuggingChat — Not in rotation (paste-required, available for future runs)

4. Phoenix Dashboard: Live Trace Data

Arize Phoenix v20.11.0 running locally during the test run. All traces routed to polyglot-free-templates project via protobuf OTLP.

📊 Projects Dashboard

Phoenix Projects Dashboard showing polyglot-free-templates project with 8 traces

The polyglot-free-templates project: 8 traces (6 template tests + 1 compare check + 1 setup), P50 latency 13.2s. The default project shows 0 traces, all telemetry routed correctly via openinference.project.name.

📋 Trace List

Phoenix Trace List showing polyglot.free_template spans

Each row is one template test. Root span polyglot.free_template contains 7 child spans (navigate, open, fill, build, send, launch, evaluate). Durations 11-14s. All OK status.

🔍 Trace Detail / Waterfall

Phoenix Trace Detail showing span waterfall with parent-child hierarchy

Parent-child span hierarchy with timing. fill_fields and build_prompt complete in milliseconds; provider.*.launch dominates latency. Each span carries structured attributes (template name, plan, goal, prompt length, provider key) queryable via GraphQL.

5. Application Test Flow Screenshots

Playwright captures of the template, fill, build, dispatch flow exercised during each traced test.

Free templates filtered
Free templates filtered (25)
Template picker showing 25 free templates available
Summarization Tool with 6 fields
Summarization Tool (6 fields)
Template with 6 dynamic input fields populated by test
Code Review with Python input
Code Review (Python input)
Code Review Assistant with Python code pasted as input
Interview Coach dispatched to Grok
Interview Coach (Grok auto-fill)
Interview Coach dispatched to Grok via ?q= auto-fill
LinkedIn Post Writer dispatched to Gemini
LinkedIn Post (Gemini paste)
LinkedIn Post Writer dispatched to Gemini via clipboard paste
Recipe Generator dispatched to Claude
Recipe Generator (Claude paste)
Recipe Generator dispatched to Claude via clipboard paste

Compare Mode Verification

Compare Mode verified as available (trial active). Locks behind Pro paywall after 3-day trial. Confirmed via DOM inspection of _appTrialExpired and _isPro. Generated its own trace in Phoenix.

Compare Mode UI verified during trial
Compare Mode UI — verified unlocked during trial period

6. Node.js Setup Guide: Protobuf OTLP + ESM

🔑 The Setup Problem: Phoenix v20+ only accepts protobuf-encoded OTLP traces. The default Node.js package (@opentelemetry/exporter-trace-otlp-http) sends JSON-encoded OTLP, which Phoenix rejects with HTTP 415 Unsupported Media Type and no useful error message. Traces silently vanish.

❌ What Most Tutorials Show

npm install @opentelemetry/exporter-trace-otlp-http
  • → Sends Content-Type: application/json
  • → Phoenix returns HTTP 415
  • → Traces silently lost
  • → No error in your app

✅ What Actually Works

npm install @opentelemetry/exporter-trace-otlp-proto
  • → Sends Content-Type: application/x-protobuf
  • → Phoenix accepts traces
  • → Spans appear in UI
  • → One package name difference

📦 ESM Compatibility — The Second Gotcha

OpenTelemetry v2 packages have ESM/CJS module conflicts that crash NodeTracerProvider in ESM projects. The fix: pin to the 1.x SDK line and use BasicTracerProvider:

@opentelemetry/api@1.9.0
@opentelemetry/sdk-trace-base@1.30.1          ← BasicTracerProvider, not NodeTracerProvider
@opentelemetry/exporter-trace-otlp-proto@0.57.2    ← protobuf, not JSON
@opentelemetry/resources@1.30.1

🏷️ Project Routing via Resource Attribute

Phoenix project assignment uses openinference.project.name as a resource attribute — not an HTTP header, query param, or endpoint path. Must be set at TracerProvider creation time:

const resource = new Resource({
  "openinference.project.name": "my-project",
});
const provider = new BasicTracerProvider({ resource });

Recommendations for Phoenix Docs

Known Instrumentation Gaps

7. Span Reference & Production Context

Span Counts Summary

Span NameCountStatus
polyglot.free_template (root)6✅ All OK
template.navigate6
template.open6
template.fill_fields6
template.build_prompt6
template.send_attempt6
provider.*.launch6
template.evaluate6
polyglot.compare_mode_check1
phoenix.test.span1
Total50

Account State During Test

PropertyValue
isProfalse
trialActivetrue (started by first send)
trialExpiredfalse
dailyFreeSends1 (DAILY_FREE_SENDS)
Compare ModeAvailable (trial active). Locks after 3-day expiry.

No production behavior modified. Real trial/entitlement system exercised. First free send triggered trial automatically.

Production Reliability (Post-Remediation)

The observability work surfaced 6 production error classes in the MCP server logs. All were remediated and deployed as v1.9.2.

15
MCP Tools
1,022
Templates
35
Languages
v1.9.2
Deployed

Errors Remediated (22 events → 0)

ClassErrorCountFixStatus
ATransport/UI crash (_meta.ui)7safe-ext-apps.js wrapperFIXED
BHTTP 429 rate limit7Backoff + jitter + Retry-AfterFIXED
CTranscription 4003Input validationFIXED
DAudio 4041AudioFetchError classFIXED
EMissing OPENAI_API_KEY3ProviderError classFIXED
FTemplate not found1TEMPLATE_NOT_FOUND handlerFIXED

8. Production MCP Server & Tool Surface

SurfaceStatusToolsNotes
Official MCP Registry✅ LIVE15Active, verified
MCP.so✅ LIVE15Verified + Featured
Glama✅ LIVE15Auto-syncs from GitHub
HuggingFace Space✅ LIVE15Static readme
Awesome MCP ServersPR closedResubmit available
MCP.DirectoryPendingAwaiting review

15 Canonical MCP Tools

Core (7)

  • search_templates
  • get_template
  • build_prompt
  • prepare_compare
  • get_language_options
  • get_subscription_status
  • open_workspace

BYOM (4)

  • get_custom_model_capabilities
  • validate_custom_model
  • run_custom_model
  • prepare_custom_compare

Language (4)

  • transcribe_audio
  • detect_language
  • translate_text
  • localize_text
Production Endpoint:
https://br-steep-leaf-ae2o29qz-mcp.compute.c-2.us-east-2.aws.neon.tech/mcp
Health: ok | Version: 1.9.2 | Tools: 15 | Templates: 1,022 | Languages: 35