Skip to content

Commit a13e08b

Browse files
committed
Support OpenAI v5
1 parent 5a13a32 commit a13e08b

File tree

7 files changed

+229
-8
lines changed

7 files changed

+229
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ See list above for supported database drivers.
102102

103103
Zen instruments the following AI SDKs to track which models are used and how many tokens are consumed, allowing you to monitor your AI usage and costs:
104104

105-
*[`openai`](https://www.npmjs.com/package/openai) 4.x
105+
*[`openai`](https://www.npmjs.com/package/openai) 5.x, 4.x
106106
*[`@aws-sdk/client-bedrock-runtime`](https://www.npmjs.com/package/@aws-sdk/client-bedrock-runtime) 3.x
107107

108108
_Note: Prompt injection attacks are currently not covered by Zen._

library/package-lock.json

Lines changed: 116 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

library/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@
102102
"mysql2-v3.12": "npm:mysql2@3.12",
103103
"needle": "^3.3.1",
104104
"node-fetch": "^2",
105+
"openai-v4": "npm:openai@^4.104.0",
106+
"openai-v5": "npm:openai@^5.7.0",
105107
"percentile": "^1.6.0",
106108
"pg": "^8.11.3",
107109
"postgres": "^3.4.4",

library/sinks/OpenAI.tests.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as t from "tap";
2+
import { startTestAgent } from "../helpers/startTestAgent";
3+
import { OpenAI as OpenAISink } from "./OpenAI";
4+
import { getMajorNodeVersion } from "../helpers/getNodeVersion";
5+
import { setTimeout } from "timers/promises";
6+
7+
export function createOpenAITests(openAiPkgName: string) {
8+
t.test(
9+
"It works",
10+
{
11+
skip:
12+
!process.env.OPENAI_API_KEY || getMajorNodeVersion() < 22
13+
? "OpenAI API key not set or Node version < 22"
14+
: undefined,
15+
},
16+
async (t) => {
17+
const agent = startTestAgent({
18+
wrappers: [new OpenAISink()],
19+
rewrite: {
20+
openai: openAiPkgName,
21+
},
22+
});
23+
24+
const { OpenAI } = require(openAiPkgName) as typeof import("openai-v5");
25+
26+
const client = new OpenAI();
27+
28+
const model = "gpt-4.1-nano";
29+
30+
const response = await client.responses.create({
31+
model: model,
32+
instructions: "Only return one word.",
33+
input: "What is the capital of Belgium?",
34+
});
35+
36+
t.same(response.output_text, "Brussels");
37+
38+
t.match(agent.getAIStatistics().getStats(), [
39+
{
40+
provider: "openai",
41+
calls: 1,
42+
tokens: {
43+
input: 23,
44+
output: 3,
45+
total: 26,
46+
},
47+
},
48+
]);
49+
50+
t.match(agent.getAIStatistics().getStats()[0].model, model); // Model name starts with the used model but may include additional information
51+
52+
await setTimeout(100);
53+
54+
const completion = await client.chat.completions.create({
55+
model: model,
56+
messages: [
57+
{ role: "developer", content: "Only return one word." },
58+
{ role: "user", content: "What is the capital of Norway?" },
59+
],
60+
});
61+
62+
t.same(completion.choices[0].message.content, "Oslo");
63+
64+
t.match(agent.getAIStatistics().getStats(), [
65+
{
66+
provider: "openai",
67+
calls: 2,
68+
tokens: {
69+
input: 46,
70+
},
71+
},
72+
]);
73+
}
74+
);
75+
}

library/sinks/OpenAI.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,37 @@ export class OpenAI implements Wrapper {
113113
return "openai";
114114
}
115115

116+
private getResponsesClass(exports: any) {
117+
if (exports.Responses) {
118+
return exports.Responses;
119+
}
120+
if (exports.OpenAI && exports.OpenAI.Responses) {
121+
return exports.OpenAI.Responses;
122+
}
123+
}
124+
125+
private getCompletionsClass(exports: any) {
126+
if (exports.Chat && exports.Chat.Completions) {
127+
return exports.Chat.Completions;
128+
}
129+
if (
130+
exports.OpenAI &&
131+
exports.OpenAI.Chat &&
132+
exports.OpenAI.Chat.Completions
133+
) {
134+
return exports.OpenAI.Chat.Completions;
135+
}
136+
}
137+
116138
wrap(hooks: Hooks) {
117139
// Note: Streaming is not supported yet
118140
hooks
119141
.addPackage("openai")
120-
.withVersion("^4.0.0")
142+
.withVersion("^5.0.0 || ^4.0.0")
121143
.onRequire((exports, pkgInfo) => {
122-
if (exports.Responses) {
123-
wrapExport(exports.Responses.prototype, "create", pkgInfo, {
144+
const responsesClass = this.getResponsesClass(exports);
145+
if (responsesClass) {
146+
wrapExport(responsesClass.prototype, "create", pkgInfo, {
124147
kind: "ai_op",
125148
modifyReturnValue: (_, returnValue, agent, subject) => {
126149
if (returnValue instanceof Promise) {
@@ -143,8 +166,9 @@ export class OpenAI implements Wrapper {
143166
});
144167
}
145168

146-
if (exports.Chat.Completions) {
147-
wrapExport(exports.Chat.Completions.prototype, "create", pkgInfo, {
169+
const completionsClass = this.getCompletionsClass(exports);
170+
if (completionsClass) {
171+
wrapExport(completionsClass.prototype, "create", pkgInfo, {
148172
kind: "ai_op",
149173
modifyReturnValue: (_, returnValue, agent, subject) => {
150174
if (returnValue instanceof Promise) {

library/sinks/OpenAI.v4.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createOpenAITests } from "./OpenAI.tests";
2+
3+
createOpenAITests("openai-v4");

library/sinks/OpenAI.v5.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createOpenAITests } from "./OpenAI.tests";
2+
3+
createOpenAITests("openai-v5");

0 commit comments

Comments
 (0)