Skip to content

Commit 59e5c8b

Browse files
feat(ts): Scaffold openinference-genai (#2298)
Introduce @arizeai/openinference-genai with utilities to map OpenTelemetry GenAI span attributes to OpenInference, plus examples and tests.
1 parent d92e2cd commit 59e5c8b

24 files changed

+1845
-66
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ sdist
1515
# Generated files
1616
.python-version
1717
.safety-project.ini
18+
.mastra
1819

1920
# Data files
2021
*.csv

js/.changeset/brave-walls-stare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@arizeai/openinference-genai": minor
3+
---
4+
5+
feat(openinference-genai): Create @arizeai/openinference-genai package

js/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pnpm-lock.yaml
22
dist
3+
.mastra
34
.next
45
__snapshots__
56
__fixtures__
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# OpenInference GenAI
2+
3+
[![npm version](https://badge.fury.io/js/@arizeai%2Fopeninference-genai.svg)](https://badge.fury.io/js/@arizeai%2Fopeninference-genai)
4+
5+
This package provides a set of utilities to convert [OpenTelemetry GenAI](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation/opentelemetry-instrumentation-genai) span attributes to OpenInference span attributes.
6+
7+
> [!WARNING]
8+
> The OpenTelemetry GenAI conventions are still incubating, and may include breaking changes at any time.
9+
> This package will attempt best effort conversions of a subset of the OpenTelemetry GenAI attributes to OpenInference attributes.
10+
> Currently, attributes reflect their definition as of October 2025.
11+
12+
## Installation
13+
14+
```shell
15+
npm install --save @arizeai/openinference-genai
16+
```
17+
18+
## Usage
19+
20+
`@arizeai/openinference-genai` can be used as a standalone set of helper functions,
21+
or in conjunction with a SpanProcessor in order to automatically convert OpenTelemetry GenAI spans to OpenInference spans.
22+
23+
### Standalone
24+
25+
You can mutate the span attributes in place by using the standalone helper functions.
26+
27+
> [!IMPORTANT]
28+
> Span mutation is not supported by the OpenTelemetry SDK, so ensure that you are
29+
> performing mutations in the last-mile of the span's lifetime (i.e. just before exporting the span in a SpanProcessor).
30+
31+
```ts
32+
import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from `@arizeai/openinference-genai`
33+
34+
// obtain a span with OpenTelemetry GenAI attributes from your tracing system
35+
const span: ReadableSpan = {/* ... */}
36+
37+
// convert the span attributes to OpenInference attributes
38+
const openinferenceAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(span.attributes)
39+
40+
// add the OpenInference attributes to the span
41+
span.attributes = {...span.attributes, ...openinferenceAttributes}
42+
```
43+
44+
### SpanProcessor
45+
46+
You can use the a custom TraceExporter to automatically convert OpenTelemetry GenAI spans to OpenInference spans.
47+
48+
See [examples/export-spans.ts](./examples/export-spans.ts) for a runnable version of the following sample code.
49+
50+
Start by installing packages
51+
52+
```shell
53+
pnpm add @opentelemetry/api @opentelemetry/core @opentelemetry/exporter-trace-otlp-proto @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-node @opentelemetry/semantic-conventions @opentelemetry/resources @arizeai/openinference-genai
54+
```
55+
56+
Create a custom TraceExporter that converts the OpenTelemetry GenAI attributes to OpenInference attributes.
57+
58+
```ts
59+
// openinferenceOTLPTraceExporter.ts
60+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
61+
import type { ReadableSpan } from "@opentelemetry/sdk-trace-base";
62+
import type { ExportResult } from "@opentelemetry/core";
63+
64+
import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from "@arizeai/openinference-genai";
65+
import type { Mutable } from "@arizeai/openinference-genai/types";
66+
67+
class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {
68+
export(
69+
spans: ReadableSpan[],
70+
resultCallback: (result: ExportResult) => void,
71+
) {
72+
const processedSpans = spans.map((span) => {
73+
const processedAttributes =
74+
convertGenAISpanAttributesToOpenInferenceSpanAttributes(
75+
span.attributes,
76+
);
77+
// optionally you can replace the entire attributes object with the
78+
// processed attributes if you want _only_ the OpenInference attributes
79+
(span as Mutable<ReadableSpan>).attributes = {
80+
...span.attributes,
81+
...processedAttributes,
82+
};
83+
return span;
84+
});
85+
86+
super.export(processedSpans, resultCallback);
87+
}
88+
}
89+
```
90+
91+
And then use it in the SpanProcessor of your choice.
92+
93+
```ts
94+
// instrumentation.ts
95+
import { resourceFromAttributes } from "@opentelemetry/resources";
96+
import {
97+
NodeTracerProvider,
98+
BatchSpanProcessor,
99+
} from "@opentelemetry/sdk-trace-node";
100+
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
101+
102+
import { SEMRESATTRS_PROJECT_NAME } from "@arizeai/openinference-semantic-conventions";
103+
104+
import { OpenInferenceOTLPTraceExporter } from "./openinferenceOTLPTraceExporter";
105+
106+
const COLLECTOR_ENDPOINT = process.env.COLLECTOR_ENDPOINT;
107+
const SERVICE_NAME = "openinference-genai-app";
108+
109+
export const provider = new NodeTracerProvider({
110+
resource: resourceFromAttributes({
111+
[ATTR_SERVICE_NAME]: SERVICE_NAME,
112+
[SEMRESATTRS_PROJECT_NAME]: SERVICE_NAME,
113+
}),
114+
spanProcessors: [
115+
new BatchSpanProcessor(
116+
new OpenInferenceOTLPTraceExporter({
117+
url: `${COLLECTOR_ENDPOINT}/v1/traces`,
118+
}),
119+
),
120+
],
121+
});
122+
123+
provider.register();
124+
```
125+
126+
## Examples
127+
128+
See the [examples](./examples) directory in this package for more executable examples.
129+
130+
To execute an example, run the following commands:
131+
132+
```shell
133+
cd js/packages/openinference-genai
134+
pnpm install
135+
pnpm -r build
136+
pnpx tsx examples/export-spans.ts
137+
```
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* eslint-disable no-console */
2+
import { resourceFromAttributes } from "@opentelemetry/resources";
3+
import {
4+
NodeTracerProvider,
5+
BatchSpanProcessor,
6+
} from "@opentelemetry/sdk-trace-node";
7+
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
8+
9+
import { SEMRESATTRS_PROJECT_NAME } from "@arizeai/openinference-semantic-conventions";
10+
11+
import { OpenInferenceOTLPTraceExporter } from "./openinferenceOTLPTraceExporter.js";
12+
import { SpanStatusCode } from "@opentelemetry/api";
13+
14+
// setup tracing provider and custom exporter
15+
16+
const COLLECTOR_ENDPOINT = process.env.COLLECTOR_ENDPOINT;
17+
const SERVICE_NAME = "openinference-genai-app";
18+
19+
export const provider = new NodeTracerProvider({
20+
resource: resourceFromAttributes({
21+
[ATTR_SERVICE_NAME]: SERVICE_NAME,
22+
[SEMRESATTRS_PROJECT_NAME]: SERVICE_NAME,
23+
}),
24+
spanProcessors: [
25+
new BatchSpanProcessor(
26+
new OpenInferenceOTLPTraceExporter({
27+
url: `${COLLECTOR_ENDPOINT}/v1/traces`,
28+
}),
29+
),
30+
],
31+
});
32+
33+
provider.register();
34+
35+
// send a test genai span
36+
const main = async () => {
37+
if (!COLLECTOR_ENDPOINT) {
38+
throw new Error("COLLECTOR_ENDPOINT is not set");
39+
}
40+
const tracer = provider.getTracer("test-genai-span");
41+
tracer.startActiveSpan("test-genai-span", (span) => {
42+
console.log("creating span");
43+
span.setAttributes({
44+
"gen_ai.provider.name": "openai",
45+
"gen_ai.operation.name": "chat",
46+
"gen_ai.request.model": "gpt-4",
47+
"gen_ai.request.max_tokens": 200,
48+
"gen_ai.request.temperature": 0.5,
49+
"gen_ai.request.top_p": 0.9,
50+
"gen_ai.response.id": "chatcmpl-CQF10eCkoFphJZJQkzzN6EkTD0AVF",
51+
"gen_ai.response.model": "gpt-4-0613",
52+
"gen_ai.usage.output_tokens": 52,
53+
"gen_ai.usage.input_tokens": 97,
54+
"gen_ai.response.finish_reasons": ["stop"],
55+
"gen_ai.input.messages":
56+
'[{"role":"user","parts":[{"type":"text","content":"Weather in Paris?"}]},{"role":"assistant","parts":[{"type":"tool_call","id":"1234","name":"get_weather","arguments":{"location":"Paris"}}]},{"role":"tool","parts":[{"type":"tool_call_response","id":"1234","response":"rainy, 57°F"}]}]',
57+
"gen_ai.output.messages":
58+
'[{"role":"assistant","parts":[{"type":"text","content":"The weather in Paris is currently rainy with a temperature of 57°F."}],"finish_reason":"stop"}]',
59+
});
60+
span.setStatus({ code: SpanStatusCode.OK });
61+
span.end();
62+
});
63+
64+
console.log("flushing and shutting down");
65+
await provider.forceFlush();
66+
await provider.shutdown();
67+
console.log("view your span at", COLLECTOR_ENDPOINT);
68+
};
69+
70+
main();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
2+
import type { ReadableSpan } from "@opentelemetry/sdk-trace-base";
3+
import type { ExportResult } from "@opentelemetry/core";
4+
5+
import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from "../src/index.js";
6+
import { Mutable } from "../src/types.js";
7+
8+
export class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {
9+
export(
10+
spans: ReadableSpan[],
11+
resultCallback: (result: ExportResult) => void,
12+
) {
13+
const processedSpans = spans.map((span) => {
14+
const processedAttributes =
15+
convertGenAISpanAttributesToOpenInferenceSpanAttributes(
16+
span.attributes,
17+
);
18+
// null will be returned in the case of an unexpected error, so we skip the span
19+
if (!processedAttributes) return span;
20+
// now we merge the processed attributes with the span attributes
21+
// optionally you can replace the entire attributes object with the
22+
// processed attributes if you want _only_ the OpenInference attributes
23+
(span as Mutable<ReadableSpan>).attributes = {
24+
...span.attributes,
25+
...processedAttributes,
26+
};
27+
return span;
28+
});
29+
30+
super.export(processedSpans, resultCallback);
31+
}
32+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"name": "@arizeai/openinference-genai",
3+
"version": "0.0.0",
4+
"private": false,
5+
"type": "module",
6+
"types": "dist/esm/index.d.ts",
7+
"description": "OpenInference utilities for converting OpenTelemetry GenAI span attributes to OpenInference span attributes",
8+
"scripts": {
9+
"prebuild": "rimraf dist",
10+
"build": "tsc --build tsconfig.esm.json && tsc-alias -p tsconfig.esm.json",
11+
"postbuild": "echo '{\"type\": \"module\"}' > ./dist/esm/package.json && rimraf dist/test dist/examples",
12+
"type:check": "tsc --noEmit",
13+
"test": "vitest"
14+
},
15+
"exports": {
16+
".": {
17+
"import": "./dist/esm/index.js"
18+
},
19+
"./attributes": {
20+
"import": "./dist/esm/attributes.js"
21+
},
22+
"./utils": {
23+
"import": "./dist/esm/utils.js"
24+
},
25+
"./types": {
26+
"types": "./dist/esm/types.d.ts"
27+
}
28+
},
29+
"files": [
30+
"dist",
31+
"src"
32+
],
33+
"keywords": [
34+
"openinference",
35+
"llm",
36+
"opentelemetry",
37+
"genai",
38+
"agent"
39+
],
40+
"author": "oss-devs@arize.com",
41+
"license": "Apache-2.0",
42+
"homepage": "https://github.com/arize-ai/openinference/tree/main/js/packages/openinference-genai",
43+
"repository": {
44+
"type": "git",
45+
"url": "git+https://github.com/Arize-ai/openinference.git"
46+
},
47+
"bugs": {
48+
"url": "https://github.com/Arize-ai/openinference/issues"
49+
},
50+
"dependencies": {
51+
"@arizeai/openinference-semantic-conventions": "workspace:*"
52+
},
53+
"devDependencies": {
54+
"@opentelemetry/api": "^1.9.0",
55+
"@opentelemetry/core": "^2.1.0",
56+
"@opentelemetry/exporter-trace-otlp-proto": "^0.206.0",
57+
"@opentelemetry/resources": "^2.1.0",
58+
"@opentelemetry/sdk-trace-base": "^2.1.0",
59+
"@opentelemetry/sdk-trace-node": "^2.1.0",
60+
"@opentelemetry/semantic-conventions": "^1.37.0",
61+
"@types/node": "^20.14.11",
62+
"vitest": "^3.1.3"
63+
},
64+
"peerDependencies": {
65+
"@opentelemetry/api": ">=1.9.0",
66+
"@opentelemetry/semantic-conventions": ">=1.37.0"
67+
}
68+
}

0 commit comments

Comments
 (0)