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:
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user