Skip to content

Commit 13d5953

Browse files
committed
openai: be defensive about response object shape
1 parent 550569a commit 13d5953

File tree

2 files changed

+70
-17
lines changed

2 files changed

+70
-17
lines changed

packages/datadog-plugin-openai/src/index.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ function retrieveModelResponseExtraction (tags, body) {
363363
tags['openai.response.parent'] = body.parent
364364
tags['openai.response.root'] = body.root
365365

366+
if (!body.permission) return
367+
366368
tags['openai.response.permission.id'] = body.permission[0].id
367369
tags['openai.response.permission.created'] = body.permission[0].created
368370
tags['openai.response.permission.allow_create_engine'] = body.permission[0].allow_create_engine
@@ -382,10 +384,14 @@ function commonLookupFineTuneRequestExtraction (tags, body) {
382384
}
383385

384386
function listModelsResponseExtraction (tags, body) {
387+
if (!body.data) return
388+
385389
tags['openai.response.count'] = body.data.length
386390
}
387391

388392
function commonImageResponseExtraction (tags, body) {
393+
if (!body.data) return
394+
389395
tags['openai.response.images_count'] = body.data.length
390396

391397
for (let i = 0; i < body.data.length; i++) {
@@ -400,7 +406,7 @@ function createAudioResponseExtraction (tags, body) {
400406
tags['openai.response.text'] = body.text
401407
tags['openai.response.language'] = body.language
402408
tags['openai.response.duration'] = body.duration
403-
tags['openai.response.segments_count'] = body.segments.length
409+
tags['openai.response.segments_count'] = defensiveArrayLength(body.segments)
404410
}
405411

406412
function createFineTuneRequestExtraction (tags, body) {
@@ -417,21 +423,24 @@ function createFineTuneRequestExtraction (tags, body) {
417423
}
418424

419425
function commonFineTuneResponseExtraction (tags, body) {
420-
tags['openai.response.events_count'] = body.events.length
426+
tags['openai.response.events_count'] = defensiveArrayLength(body.events)
421427
tags['openai.response.fine_tuned_model'] = body.fine_tuned_model
422-
tags['openai.response.hyperparams.n_epochs'] = body.hyperparams.n_epochs
423-
tags['openai.response.hyperparams.batch_size'] = body.hyperparams.batch_size
424-
tags['openai.response.hyperparams.prompt_loss_weight'] = body.hyperparams.prompt_loss_weight
425-
tags['openai.response.hyperparams.learning_rate_multiplier'] = body.hyperparams.learning_rate_multiplier
426-
tags['openai.response.training_files_count'] = body.training_files.length
427-
tags['openai.response.result_files_count'] = body.result_files.length
428-
tags['openai.response.validation_files_count'] = body.validation_files.length
428+
if (body.hyperparams) {
429+
tags['openai.response.hyperparams.n_epochs'] = body.hyperparams.n_epochs
430+
tags['openai.response.hyperparams.batch_size'] = body.hyperparams.batch_size
431+
tags['openai.response.hyperparams.prompt_loss_weight'] = body.hyperparams.prompt_loss_weight
432+
tags['openai.response.hyperparams.learning_rate_multiplier'] = body.hyperparams.learning_rate_multiplier
433+
}
434+
tags['openai.response.training_files_count'] = defensiveArrayLength(body.training_files)
435+
tags['openai.response.result_files_count'] = defensiveArrayLength(body.result_files)
436+
tags['openai.response.validation_files_count'] = defensiveArrayLength(body.validation_files)
429437
tags['openai.response.updated_at'] = body.updated_at
430438
tags['openai.response.status'] = body.status
431439
}
432440

433441
// the OpenAI package appears to stream the content download then provide it all as a singular string
434442
function downloadFileResponseExtraction (tags, body) {
443+
if (!body.file) return
435444
tags['openai.response.total_bytes'] = body.file.length
436445
}
437446

@@ -472,20 +481,26 @@ function createRetrieveFileResponseExtraction (tags, body) {
472481
function createEmbeddingResponseExtraction (tags, body) {
473482
usageExtraction(tags, body)
474483

484+
if (!body.data) return
485+
475486
tags['openai.response.embeddings_count'] = body.data.length
476487
for (let i = 0; i < body.data.length; i++) {
477488
tags[`openai.response.embedding.${i}.embedding_length`] = body.data[i].embedding.length
478489
}
479490
}
480491

481492
function commonListCountResponseExtraction (tags, body) {
493+
if (!body.data) return
482494
tags['openai.response.count'] = body.data.length
483495
}
484496

485497
// TODO: Is there ever more than one entry in body.results?
486498
function createModerationResponseExtraction (tags, body) {
487499
tags['openai.response.id'] = body.id
488500
// tags[`openai.response.model`] = body.model // redundant, already extracted globally
501+
502+
if (!body.results) return
503+
489504
tags['openai.response.flagged'] = body.results[0].flagged
490505

491506
for (const [category, match] of Object.entries(body.results[0].categories)) {
@@ -501,6 +516,8 @@ function createModerationResponseExtraction (tags, body) {
501516
function commonCreateResponseExtraction (tags, body, store) {
502517
usageExtraction(tags, body)
503518

519+
if (!body.choices) return
520+
504521
tags['openai.response.choices_count'] = body.choices.length
505522

506523
store.choices = body.choices

packages/datadog-plugin-openai/test/index.spec.js

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ describe('Plugin', () => {
5555
describe('createCompletion()', () => {
5656
let scope
5757

58-
before(() => {
58+
after(() => {
59+
nock.removeInterceptor(scope)
60+
scope.done()
61+
})
62+
63+
it('makes a successful call', async () => {
5964
scope = nock('https://api.openai.com:443')
6065
.post('/v1/completions')
6166
.reply(200, {
@@ -87,14 +92,7 @@ describe('Plugin', () => {
8792
'x-ratelimit-reset-tokens', '3ms',
8893
'x-request-id', '7df89d8afe7bf24dc04e2c4dd4962d7f'
8994
])
90-
})
91-
92-
after(() => {
93-
nock.removeInterceptor(scope)
94-
scope.done()
95-
})
9695

97-
it('makes a successful call', async () => {
9896
const checkTraces = agent
9997
.use(traces => {
10098
expect(traces[0][0]).to.have.property('name', 'openai.request')
@@ -188,6 +186,44 @@ describe('Plugin', () => {
188186
]
189187
})
190188
})
189+
190+
it('should not throw with empty response body', async () => {
191+
scope = nock('https://api.openai.com:443')
192+
.post('/v1/completions')
193+
.reply(200, {}, [
194+
'Date', 'Mon, 15 May 2023 17:24:22 GMT',
195+
'Content-Type', 'application/json',
196+
'Content-Length', '349',
197+
'Connection', 'close',
198+
'openai-model', 'text-davinci-002',
199+
'openai-organization', 'kill-9',
200+
'openai-processing-ms', '442',
201+
'openai-version', '2020-10-01',
202+
'x-ratelimit-limit-requests', '3000',
203+
'x-ratelimit-limit-tokens', '250000',
204+
'x-ratelimit-remaining-requests', '2999',
205+
'x-ratelimit-remaining-tokens', '249984',
206+
'x-ratelimit-reset-requests', '20ms',
207+
'x-ratelimit-reset-tokens', '3ms',
208+
'x-request-id', '7df89d8afe7bf24dc04e2c4dd4962d7f'
209+
])
210+
211+
const checkTraces = agent
212+
.use(traces => {
213+
expect(traces[0][0]).to.have.property('name', 'openai.request')
214+
})
215+
216+
const result = await openai.createCompletion({
217+
model: 'text-davinci-002',
218+
prompt: 'Hello, ',
219+
suffix: 'foo',
220+
stream: true,
221+
})
222+
223+
await checkTraces
224+
225+
clock.tick(10 * 1000)
226+
})
191227
})
192228

193229
describe('createEmbedding()', () => {

0 commit comments

Comments
 (0)