Skip to content

Commit 6d8907d

Browse files
committed
Add tests and docs
1 parent 13c15de commit 6d8907d

File tree

8 files changed

+1414
-6
lines changed

8 files changed

+1414
-6
lines changed

js/packages/openinference-mastra/README.md

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ A typical Mastra project will already have OpenTelemetry and related packages in
1414

1515
## Usage
1616

17-
`@arizeai/openinference-mastra` provides a set of utilities to help you ingest Mastra spans into platforms and works in conjunction with Mastra's OpenTelemetry support. To get started, you will need to add OpenTelemetry support to your Mastra project according to the [Mastra Observability guide](https://mastra.ai/en/reference/observability/providers), or, follow along with the rest of this README.
17+
`@arizeai/openinference-mastra` provides a set of utilities to help you ingest Mastra spans into the Phoenix platform (and any other OpenInference-compatible platform) and works in conjunction with Mastra's OpenTelemetry support. To get started, you will need to add OpenTelemetry support to your Mastra project according to the [Mastra Observability guide](https://mastra.ai/en/reference/observability/providers), or, follow along with the rest of this README.
1818

1919
To process your Mastra spans add an `OpenInferenceOTLPTraceExporter` to your `telemetry` configuration within your `Mastra` instance.
2020

@@ -26,7 +26,7 @@ export PHOENIX_API_KEY="your-api-key"
2626

2727
```typescript
2828
import { Mastra } from "@mastra/core";
29-
import { OpenInferenceOTLPTraceExporter } from "@arizeai/openinference-mastra";
29+
import { OpenInferenceOTLPTraceExporter, isOpenInferenceSpan } from "@arizeai/openinference-mastra";
3030

3131
export const mastra = new Mastra({
3232
// ... other config
@@ -37,7 +37,12 @@ export const mastra = new Mastra({
3737
type: "custom",
3838
exporter: new OpenInferenceOTLPTraceExporter({
3939
collectorEndpoint: process.env.PHOENIX_COLLECTOR_ENDPOINT,
40+
// optional: add bearer auth token if Phoenix or other platform requires it
4041
apiKey: process.env.PHOENIX_API_KEY,
42+
// optional: filter out http, and other node service specific spans
43+
// they will still be exported to Mastra, but not to the target of
44+
// this exporter
45+
spanFilter: isOpenInferenceSpan,
4146
}),
4247
},
4348
},
@@ -48,4 +53,74 @@ For general details on Mastra's OpenTelemetry support see the [Mastra Observabil
4853

4954
## Examples
5055

51-
TODO: Add examples
56+
### Weather Agent
57+
58+
To setup the canonical Mastra weather agent example, and then ingest the spans into Phoenix, follow the steps below.
59+
60+
- Create a new Mastra project
61+
62+
```shell
63+
npm create mastra@latest
64+
# answer the prompts, include agent, tools, and the example when asked
65+
cd chosen-project-name
66+
npm install --save @arizeai/openinference-mastra
67+
# export some variables for mastra to use later on
68+
export PHOENIX_COLLECTOR_ENDPOINT="https://localhost:6006/v1/traces"
69+
export PHOENIX_API_KEY="your-api-key"
70+
export OPENAI_API_KEY="your-openai-api-key"
71+
```
72+
73+
- Add the OpenInferenceOTLPTraceExporter to your Mastra project
74+
75+
```typescript
76+
// chosen-project-name/src/index.ts
77+
import { Mastra } from "@mastra/core/mastra";
78+
import { createLogger } from "@mastra/core/logger";
79+
import { LibSQLStore } from "@mastra/libsql";
80+
import {
81+
isOpenInferenceSpan,
82+
OpenInferenceOTLPTraceExporter,
83+
} from "@arizeai/openinference-mastra";
84+
85+
import { weatherAgent } from "./agents";
86+
87+
export const mastra = new Mastra({
88+
agents: { weatherAgent },
89+
storage: new LibSQLStore({
90+
url: ":memory:",
91+
}),
92+
logger: createLogger({
93+
name: "Mastra",
94+
level: "info",
95+
}),
96+
telemetry: {
97+
enabled: true,
98+
serviceName: "weather-agent",
99+
export: {
100+
type: "custom",
101+
exporter: new OpenInferenceOTLPTraceExporter({
102+
apiKey: process.env.PHOENIX_API_KEY,
103+
collectorEndpoint: process.env.PHOENIX_COLLECTOR_ENDPOINT,
104+
spanFilter: isOpenInferenceSpan,
105+
}),
106+
},
107+
},
108+
});
109+
```
110+
111+
- Run the agent
112+
113+
```shell
114+
npm run dev
115+
```
116+
117+
- Send a chat message to the agent in the playground [http://localhost:4111/agents/weatherAgent/chat/](http://localhost:4111/agents/weatherAgent/chat/)
118+
119+
![weather agent chat](./docs/mastra-weather-agent.png)
120+
121+
- After a few moments, you should see the spans for the agent's request and response in Phoenix.
122+
- Not sure how to run the Phoenix collector? [Check out the Phoenix docs](https://docs.arize.com/phoenix/self-hosting/deployment-options/docker#docker).
123+
124+
![weather agent spans](./docs/mastra-weather-agent-spans.png)
125+
126+
You've done it! For next steps, check out the [Mastra docs](https://mastra.ai/en/docs) to learn how to add more agents, tools, and storage options to your project.
Loading
Loading
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
2+
3+
let debugSpans: Pick<
4+
ReadableSpan,
5+
| "name"
6+
| "attributes"
7+
| "parentSpanId"
8+
| "kind"
9+
| "status"
10+
| "resource"
11+
| "startTime"
12+
| "endTime"
13+
>[] = [];
14+
15+
/**
16+
* DEBUG
17+
*
18+
* Accumulate items across invocations until the item has no parentId, then dump items to json file
19+
* $HOME/debug-mastra-instrumentation/spans-{new Date().toISOString()}.json
20+
*/
21+
export const debug = async (items: ReadableSpan[]) => {
22+
// only import fs if we need it
23+
// this allows the module to be used in environments that don't have fs
24+
const fs = await import("node:fs");
25+
debugSpans.push(
26+
// @ts-expect-error -just grabbing incomplete fields for testing
27+
...items
28+
.map((i) => ({
29+
name: i.name,
30+
attributes: i.attributes,
31+
parentSpanId: i.parentSpanId,
32+
kind: i.kind,
33+
status: i.status,
34+
resource: {},
35+
startTime: i.startTime,
36+
endTime: i.endTime,
37+
}))
38+
.filter((i) =>
39+
["post", "agent", "ai"].some((prefix) =>
40+
i.name.toLocaleLowerCase().startsWith(prefix),
41+
),
42+
),
43+
);
44+
const root = items.find((i) => i.parentSpanId == null);
45+
if (root) {
46+
fs.mkdirSync("debug-mastra-instrumentation", { recursive: true });
47+
fs.writeFileSync(
48+
`debug-mastra-instrumentation/${encodeURIComponent(root.name)}-${new Date().toISOString()}.json`,
49+
JSON.stringify(debugSpans, null, 2),
50+
);
51+
debugSpans = [];
52+
}
53+
};
Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
1+
import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
12
import { OpenInferenceOTLPTraceExporter } from "../src/OpenInferenceTraceExporter.js";
3+
import weatherAgentSpans from "./__fixtures__/weatherAgentSpans.json";
4+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
5+
6+
vi.mock(import("@opentelemetry/exporter-trace-otlp-proto"), () => {
7+
const mockedClass = vi.fn();
8+
mockedClass.prototype.export = vi.fn();
9+
return {
10+
OTLPTraceExporter: mockedClass,
11+
};
12+
});
213

314
describe("OpenInferenceTraceExporter", () => {
15+
afterEach(() => {
16+
vi.resetAllMocks();
17+
});
18+
419
it("should initialize without throwing an error", () => {
520
new OpenInferenceOTLPTraceExporter({
6-
collectorEndpoint: "http://localhost:6006/v1/traces",
21+
collectorEndpoint: "http://example.com/v1/traces",
22+
apiKey: "test-api-key",
23+
});
24+
});
25+
26+
// Quickly capture a known working state of the instrumentation to ensure
27+
// we don't regress.
28+
// TODO: Replace with a more fine-grained test that is easier to update over
29+
// time with the changes in the instrumentation.
30+
it("(snapshot) should export spans with openinference properties", async () => {
31+
const exporter = new OpenInferenceOTLPTraceExporter({
32+
collectorEndpoint: "http://example.com/v1/traces",
733
apiKey: "test-api-key",
834
});
35+
exporter.export(weatherAgentSpans as unknown as ReadableSpan[], () => {});
36+
await expect(
37+
// @ts-expect-error - mock.calls is provided by vitest
38+
OTLPTraceExporter.prototype.export.mock.calls,
39+
).toMatchFileSnapshot(
40+
`./__snapshots__/OpenInferenceTraceExporter.test.ts.export.json`,
41+
);
942
});
1043
});

0 commit comments

Comments
 (0)