Revert OpenCode dual-export — Phoenix only

The OpenLIT secondary exporter regressed tool-call parsing in OpenCode:
OpenLIT's image doesn't currently host an OTLP receiver on 4328, so the
exporter retries failed silently and the failures cascaded into the AI
SDK's telemetry pipeline. Symptom: model output came through as raw
Qwen3-Coder XML tool-call text instead of being parsed into actual tool
invocations.

Re-add when openlit.yml gets an otel-collector sidecar that actually
listens on the receiver ports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 13:19:39 -04:00
parent 5d3fce22a1
commit 1816ae2458
2 changed files with 19 additions and 34 deletions

View File

@@ -40,17 +40,8 @@ export const PhoenixBridge = async ({ project, directory, worktree }) => {
// Phoenix 15.x serves OTLP/HTTP at /v1/traces on the same port as the UI // Phoenix 15.x serves OTLP/HTTP at /v1/traces on the same port as the UI
// (6006). Earlier versions used a separate 4318 — override here if you // (6006). Earlier versions used a separate 4318 — override here if you
// ever pin Phoenix < 15.0. // ever pin Phoenix < 15.0.
const phoenixEndpoint = const endpoint =
process.env.PHOENIX_OTLP_ENDPOINT || "http://framework:6006/v1/traces"; process.env.PHOENIX_OTLP_ENDPOINT || "http://framework:6006/v1/traces";
// OpenLIT's OTLP/HTTP receiver, host-mapped to 4328 in
// pyinfra/framework/compose/openlit.yml. Set OPENLIT_OTLP_ENDPOINT to
// an empty string (or "off") to disable the secondary export.
const openlitEndpointRaw =
process.env.OPENLIT_OTLP_ENDPOINT === undefined
? "http://framework:4328/v1/traces"
: process.env.OPENLIT_OTLP_ENDPOINT;
const openlitEndpoint =
openlitEndpointRaw && openlitEndpointRaw !== "off" ? openlitEndpointRaw : null;
const serviceName = process.env.PHOENIX_SERVICE_NAME || "opencode"; const serviceName = process.env.PHOENIX_SERVICE_NAME || "opencode";
// NodeSDK's `serviceName` constructor option is ignored in some // NodeSDK's `serviceName` constructor option is ignored in some
// versions; setting OTEL_SERVICE_NAME forces the resource attribute // versions; setting OTEL_SERVICE_NAME forces the resource attribute
@@ -58,15 +49,12 @@ export const PhoenixBridge = async ({ project, directory, worktree }) => {
if (!process.env.OTEL_SERVICE_NAME) { if (!process.env.OTEL_SERVICE_NAME) {
process.env.OTEL_SERVICE_NAME = serviceName; process.env.OTEL_SERVICE_NAME = serviceName;
} }
log( log(`endpoint=${endpoint} serviceName=${serviceName}`);
`phoenix=${phoenixEndpoint} openlit=${openlitEndpoint || "off"} serviceName=${serviceName}`,
);
// Dynamic imports so a missing dep produces a warning, not a freeze. // Dynamic imports so a missing dep produces a warning, not a freeze.
let NodeSDK, let NodeSDK,
OTLPTraceExporter, OTLPTraceExporter,
BatchSpanProcessor, BatchSpanProcessor,
SimpleSpanProcessor,
trace, trace,
context, context,
diag, diag,
@@ -79,9 +67,7 @@ export const PhoenixBridge = async ({ project, directory, worktree }) => {
({ OTLPTraceExporter } = await import( ({ OTLPTraceExporter } = await import(
"@opentelemetry/exporter-trace-otlp-proto" "@opentelemetry/exporter-trace-otlp-proto"
)); ));
({ BatchSpanProcessor, SimpleSpanProcessor } = await import( ({ BatchSpanProcessor } = await import("@opentelemetry/sdk-trace-base"));
"@opentelemetry/sdk-trace-base"
));
({ trace, context, diag, DiagLogLevel } = await import( ({ trace, context, diag, DiagLogLevel } = await import(
"@opentelemetry/api" "@opentelemetry/api"
)); ));
@@ -116,18 +102,16 @@ export const PhoenixBridge = async ({ project, directory, worktree }) => {
// NodeSDK accepts `serviceName` directly, sidestepping the Resource API // NodeSDK accepts `serviceName` directly, sidestepping the Resource API
// (which broke between @opentelemetry/resources v1.x and v2.x). // (which broke between @opentelemetry/resources v1.x and v2.x).
// BatchSpanProcessor batches spans and flushes every ~5s — fine in // Single Phoenix destination — the dual-export to OpenLIT regressed
// steady state. Each destination gets its own processor + exporter so // tool-call parsing in OpenCode (the failing OpenLIT exporter cascaded
// a hiccup at one (e.g. OpenLIT down) doesn't block the other. // into the AI SDK telemetry pipeline). Re-add once OpenLIT has a
const spanProcessors = [ // proper OTLP receiver (otel-collector sidecar in openlit.yml).
new BatchSpanProcessor(new OTLPTraceExporter({ url: phoenixEndpoint })), const sdk = new NodeSDK({
]; serviceName,
if (openlitEndpoint) { spanProcessors: [
spanProcessors.push( new BatchSpanProcessor(new OTLPTraceExporter({ url: endpoint })),
new BatchSpanProcessor(new OTLPTraceExporter({ url: openlitEndpoint })), ],
); });
}
const sdk = new NodeSDK({ serviceName, spanProcessors });
sdk.start(); sdk.start();
log("sdk.start() returned"); log("sdk.start() returned");

View File

@@ -75,17 +75,18 @@ The plugin uses `@opentelemetry/exporter-trace-otlp-proto` (not `-http`)
because Phoenix's OTLP receiver only speaks protobuf — the JSON variant because Phoenix's OTLP receiver only speaks protobuf — the JSON variant
returns 415. returns 415.
Spans are dual-exported: Phoenix (per-trace waterfall) and OpenLIT (fleet Spans go to Phoenix only. Earlier versions of this plugin dual-exported
metrics). Each destination has its own batch processor so a hiccup at to OpenLIT as well, but OpenLIT's container doesn't currently host an
one doesn't block the other. OTLP receiver — the failing exporter cascaded into OpenCode's tool-call
parsing pipeline and broke tool use. Re-enable once `openlit.yml` adds
an `otel-collector` sidecar.
Defaults can be overridden via env vars (set before launching opencode): Defaults can be overridden via env vars (set before launching opencode):
| Variable | Default | Purpose | | Variable | Default | Purpose |
|---|---|---| |---|---|---|
| `PHOENIX_OTLP_ENDPOINT` | `http://framework:6006/v1/traces` | Phoenix HTTP target | | `PHOENIX_OTLP_ENDPOINT` | `http://framework:6006/v1/traces` | Phoenix HTTP target |
| `OPENLIT_OTLP_ENDPOINT` | `http://framework:4328/v1/traces` | OpenLIT HTTP target. Set to `off` to disable. | | `PHOENIX_SERVICE_NAME` | `opencode` | Phoenix project name |
| `PHOENIX_SERVICE_NAME` | `opencode` | Service / project name (both backends) |
| `PHOENIX_OTEL_DEBUG` | unset | `1` to surface OTel internal logs | | `PHOENIX_OTEL_DEBUG` | unset | `1` to surface OTel internal logs |
### Verifying ### Verifying