From 7ff6bc5edb8a1822ed4cd6e2efec455a9f9c6bd3 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 13 Apr 2026 16:18:34 -0400 Subject: [PATCH] fix(openai): trace chat streaming audio attachments Preserve chat completion delta.audio fields in streamed OpenAI spans and materialize the audio payload as a Braintrust attachment instead of logging raw base64 data. Also apply the same attachment materialization to non-streaming chat completion audio output and add VCR-backed regression coverage for the streaming path plus focused unit tests for the audio aggregation logic. --- ...aming_sync_preserves_audio_attachment.yaml | 156 +++++++++++++++++ .../integrations/openai/test_openai.py | 158 ++++++++++++++++++ .../braintrust/integrations/openai/tracing.py | 110 +++++++++++- py/src/braintrust/integrations/utils.py | 1 + 4 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 py/src/braintrust/integrations/openai/cassettes/test_openai_chat_streaming_sync_preserves_audio_attachment.yaml diff --git a/py/src/braintrust/integrations/openai/cassettes/test_openai_chat_streaming_sync_preserves_audio_attachment.yaml b/py/src/braintrust/integrations/openai/cassettes/test_openai_chat_streaming_sync_preserves_audio_attachment.yaml new file mode 100644 index 00000000..28b1b3e4 --- /dev/null +++ b/py/src/braintrust/integrations/openai/cassettes/test_openai_chat_streaming_sync_preserves_audio_attachment.yaml @@ -0,0 +1,156 @@ +interactions: +- request: + body: '{"messages":[{"role":"user","content":"Say exactly hello."}],"model":"gpt-4o-audio-preview","audio":{"voice":"alloy","format":"pcm16"},"modalities":["text","audio"],"stream":true,"stream_options":{"include_usage":true},"temperature":0}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '235' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.31.0 + X-Stainless-Arch: + - arm64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - MacOS + X-Stainless-Package-Version: + - 2.31.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.13.3 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[{"index":0,"delta":{"role":"assistant","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"QBu7xZVtVShZN"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[{"index":0,"delta":{"content":null,"audio":{"id":"audio_69dd4733bc6c8191b1c8e00bc0d7a47f","transcript":"Hello"}},"finish_reason":null}],"usage":null,"obfuscation":"P1g"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[{"index":0,"delta":{"audio":{"transcript":"."}},"finish_reason":null}],"usage":null,"obfuscation":"sy4K"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"VCc2Vp"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"XqzvK1"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"Tgib10"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"Fdje6e"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"iwOH6e"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"OHAT1K"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"gJ2Rk0"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":null,"audio":{"id":"audio_69dd4733bc6c8191b1c8e00bc0d7a47f","data":"FQAPAA4AFwAMABYADgATABIAEwAXAAoADwAHABAACwARAAkADwAOAAoADAADAAwAAwAPAAMACAACAAgABQACAAkABgAEAAAAAwAAAAYA/v8FAPz//v/9//v/+//z////8v/+//H/+f/1//n/+P/x//r/8//8/+r/+//p//X/7P/y/+7/6f/y/+f/8v/l//b/5v/z/+r/5//s/+T/8P/f/+7/4v/o/+T/4P/r/97/6//c/+n/4f/h/+j/2//q/9z/5//Z/+H/5f/f/+T/2v/l/9j/5v/f/+L/3P/f/+L/4//j/+D/5P/f/+T/4P/m/97/4v/e/+P/4f/f/9//4P/l/+D/4//j/+H/5P/k/+L/6f/l/+X/5//j/+b/5P/r/+P/6//k/+v/6P/t/+r/6f/t/+j/6f/o/+z/7//v//L/8f/y//L/8f/w//H/9//2//L/9P/u//H/7//0//H/8v/z/+3/9P/u//T/8//x//j/7//8//L////4/wAAAAD7/wAA+v8KAP7/BwD//wkAAAAEAAYAAgAIAP7/CwD+/wsAAQAMAAEACAADAAQAEgALABMACgAYAA0AHwAUABoAHAAaACgAGgAwAB4ANQAlACoAJwAkAC0AIgA5ACsAMgAuADgAMgArADQAJwAzAC4ANQAuADMAOQAxADEAMAA3ADkAPQBCAEgATgBLAEsASwBKAE4ASgBQAFMAVgBTAFMARQA/ADsAMAA4ADIAPAAvADEALAAlACIAIQAtAC0APAA9AEgAQgA8AEAANgBEAEQATABJAFEATQBDAEAAJAAjAAwAFgAPABEAHAAWACEAGAAaAAUABgD5//3/CwAXACcAMgA7AEAANwAzACEAHQAbABsAJAAfACsAIAAlACAAFQAdACoALwAmACkAFAAQAP//AAABAAsAKQAeACUAAQD6/9T/0f/M/9v/9//8/xkABQAWAA4AEAATABAAGAAMAA4A/P/7/xIAHwBAAEIAPAAmAAEA4P+1/7H/qv/B/9j/3//g/8b/r/+j/6L/r/+u/7P/o/+S/4//i/+k/7P/1f/0/wcAGwAAAOf/xP/E/9n/6/8JAPr////T/8D/m/+B/3n/Z/+D/33/mf+N/5n/k/+Q/6D/nv+q/4X/f/9o/3f/hv+O/7T/zf/j/8T/j/9Q/xv/Fv8e/1X/f/+d/5f/bP9B//7+2v64/rj+yP7+/j7/bv+K/5z/pP+M/1P/Av/F/q3+0f7y/hP/Hf8y/yj/EP/K/n/+Yf4//nL+w/5E/4//rP+p/4n/jP+E/4X/bP93/5j/pf+P/0b/Jv8x/2//0/8nADkA3/9Z/+H+x/4E/2f/s//7/08AkgCKACYAjv8d//n+H/9A/2f/lP/O/wcAKwBDABoA4P91/1j/pP82AKwAlAB7AGcAtQDxAO0ApwAtAOf/vP/h/+b/0P+1/9v/QwDKABoB4QBbAPv/BwBYAGQA9f9S/+n+Nf/C/0YAQQAcAC0ASgA+AKX/Nv/G/vT+k/9+AEoBaAFaAeoA5QCMAEgAtf9w//j/3ADpARkC5AE7AakATQD//8v/TP/V/q/+M/9sAGkBDwItAh8C+AEvAUAA+/7Z/r3/UQHiAooD2wNXA64CfAF4AOb/vf/8/zgArQC8ALEAaABcAGYATgAHAGX/8P7f/oH/RgCkAKIA0gB4AQgC8AHyALf/+/5E/0kAZQH7AQkCswE7AaQAHQBf/7P+ZP7R/tL/dgCeAGoArgAKAWYBQwFhACn/Sv42/zgBRQOYAx8DoQIuAiIBjP+Y/jr+kf6p/uf+2/6I/tD9pv3B/gMAvADQ/73+Fv4u/wkBkQKCA3kDrwNjA9sCZwH9/4n/6v/qADkB9wCb//H9t/zX/F7+bv9///L+6P4a/w//R/8ZAHsB1AL0A60EiQRbA7YBzADGAOQAeQDa/4v/vv9IAHEAEQCB/8z/oAANAZMAgv/X/nj+h/7F/qX/1QC6ASYCBgK6AdgAzP8J/wj/XP9Q/w3/FP8cABQBhAEFASkABf/2/cH99v29/lj/MwA2Ac8BgAEsAB//F/7A/JD7vfu1/cX/lAAsAJ3/zv/i/6b/Df+q/r/+mP7R/sv+kf+mAPABEQPDAiAC1wBDAHv/x/53/kn+nf4S/sr9n/0R/hj+tf3t/QP+If5V/S39uv31/kgAEAHQAWMBewDb/sf9rf3A/tAAcgKCA2YDiAJgAF3+LP3y/Dn9+P1BAHECzQJmAPj9OP2+/d/9RP09/XL9w/2p/Z793v0L/g3+Qf4u/38ARgEIAVEAYgBtAf0BAwGp/o79mv7UAFMBLwD0/nv+1P7m/g8AzAB1AIv/xP9FAakB7gBYAKkBDgNKAwUCqP85/Vn7L/xL/ncAIQFmAPD/l//W/6H/g//t/1YBCgOHAycDHwLvAQAC0AE4AewATAFsAfEALwDhAAACPgL5AIz/Df9J/vX8fvtP/HL+WACCAIn/Xf+k/3sBLAOiBPQEugPHARAAwv90/9P/lgBIAoAD/gLaAA7+Uvxy+3n8Yf7RADICeQMQBVoFEwR5Acv/Jv6T/eT8WfyC/JT9jgBpAzcFXASaAi8B0ABKAIv+8vzN/JL+SgB8AWwCLgOjAp0BGAGMAHH/Sf4b/vX+1v8HANT/Wf99/9j/0P8p/xP+/Pxu+277hP1YAOsBGAI9A3cEXQQHA7MAaf4S/R/+xgC3AuoCrQIyA1YD8wKvAVYAIf+i/W38nPvr+x790v7ZACIDbARQA6oB5wEMA0ADmAFIADAAAgCi/2/+9/2r/r4AfQIlAsr/1/y++2v7iPvY+/z9IwBeATcCAQM0BKQDlAJTAk4E0wW/BO0BIABIAXECrgEs/pL8Cf5v/aL6gfdd+ET6Fft//Jb+XwGJAZEAGQAwAUADFgWsBccEnwMmAqUAqf6g/Vv9Ev2u/DX8EvyC/If96f37/bT/7gGaAFb8sPma+zH/NgHWASMCugKUA+8DzgK5/+j8yvxd/qX+hP3i/KD9swAuA8kDpQLsAXwB7wDpAHkB+QCY/cj6+vqD/Tf++/05/88CJQa6BdgCJP8L/gD+Kv6A/rAAUQNdAoz+F/sp+8f8Ev7//kQA/gCh/+38Uvq6++//hwSABrsFEAX2Aqb/+PtQ+8P+CAJFBCEEQwNeAeH+LP6Y/uP/JADw/6r/UP/Y/i7+xv5jABUDCgRRA4kBMwA3AD8ArACNAQ4CnQEfAfn/rP6W/Tn+iP80AcMD3wTYBBsCEgCr/3v+7Pz/+jf8ff7KAF4B+gEzAtIBLwBj/Z/8uv2BAKsBHgOYBGkF9wPJAJ7+Vf6g/+4AdgHUAb8CswEaABj+UP5i/1b/2fyX+jn8EP6Q/pb95f7PAWEEHAQ6At8Avv9p/tj8Cf6u/4kBRwPOBEMFYwOFAVP/X/3a+wL64/mh+wD9iP4SARQEPwVWBIkERAVZBDIBfP07/Eb9Nf6d/nL/9AB2AbQABAD4/A/71/ou/HH+GP8WALEAUAHr/9//fgG8AuIAzv2r/Yv/qAHmAa4DsAUOB9UFygGV/sn7GfqS+vP7pv1KAMoB5wIEA7MDewO+AiEB9f4y/m/8ZfvV+mT9ewAwBO8FUgZbBQkCJQA2/ZP8s/w6/TL+qP9+AhUEHwWxBKIECQUMBCIC+P8M/hb8qvt2/Jb9cf4C/pH+MP95/zn+EvyJ+r/4F/kS+m/95P91AKMBLgRqB+gHxQbtBHQE2QLuADoAWQBdAJv/DwFBA/4DegC++/74iPcu+AL31vaF+A77Zf6PAOIBbwKuBBUFTAUzBdcDwgCw/Jb7KvyA/dj8nP6xAOIBmwAC/Qj7N/mC+KL4nvx7/mb/GgHtAoMGqAdkCFAGpgO/AB3+4vz7+Qj5wfgJ/I3+Mf7H/vj+3/+b/mP/ZP6D/Qn9XfwQ/9wAYgTmBXQHHQevBUEEjwPdA7AAif7b/a79H/3m/YT+bwC1AXMBhQHMAAcABP50/hP+rv/Z/+0A4wPEBNwFzwVzBbQEKQU5A0YACv8h/zz/WP4x/hb/VQCI/6v9yf6/AJUClwLFAH0AVgCn/nH8zfxL/UH/PAHNANcBywHO/1v9vvxq/uMA4wAu/1L/eP13/Sn+4Pwo/cf+4QAdAcIBbP9y/sL9Bvyf/If8VP/6/qj8h/kX+Z755Pmm+or53fup++j7AfyZ/Fj9Pv1P/2f/bAA2/+D/dQH2AtgG+AaaByQGzwMvACH+Yv7o/ckAGwGeAfUBagPHBcUGiAbgBmkKTArPBiQDwQN4BjgGWAXgBjELRAxzCvIIGArQDGsN0w08DnYPygzKCvoKjQsFDckMqQssCFMGBASPAm8CagICBOkEKQbsBMsCGQKxApgD/gQ/B64GOwT7AAb+G/3C+0764Pp1+7D7A/q+91T2pfV/9VH1svQV8+fyYPCi7cXraus26wXsGe0Y6/zqyOof7azuO/Di70vxgfPC9SL4GvVY82Tz/vEA8Pzrc+TA5WXofuka7lHxx/O39ZP4lPzK/7P+of/gAWEE/QVBBpEJKw7QEawTcRvtIaojICImH78gHiEIHlcXpBbEFsoXMRc+FFUUHROkFAIU1BGiDfYKgAkNBnMESAQqBekIIQrPCsUNyQ8fEUAU/xSJFA8TiA8qDcwKhQguBAUDNgFT/3n+bfsJ/IT7Zvph+mz6vflf+TT4APir+rP69Piq9QLx2O507hvsvedb5DrkPOic6ajnJuur7WPxYvLs7qjtb+yP6w/pQegB5ezkheOn4HHhnt8Q3grfjN8h4hfireEk49TkDOdM7oDzZ/ji/jwChA2gFCYdHCK/J5Qp5yhPJ+cfZB2gFBsRQg7XDuQNdAzmCqUJdAsMCXUKHAe5BTICKf8QACMA1QBhA8cJUBAiFSwZXh18IQUksiHwIQ0fQxpkFKkQgBDPDxkQUw1yDYwMvwuLCbkG5gNc/tz46PWm89vyRfRG9lr7uQFGBpQK3QyyDa8MlQouB+oBSfw795bzifDs7j7tVOvZ6iXr/OsA7lzuA+/J7oXuZO+67XnrP+kk5hDlxeV842fgnN5R3p3dV9453l3jSOcn68PtOfEr9PXvxPCc8xD+lgM0Ba4KSxJ+HQkgvSRTKCsp+CfAJewhvxsSFcoKkQZoAOP8qPne9m745vou/ygCyAelCncKJAn+CWYKkwqBCzoLgAyVDMEMpgz+D6ATyhfGG+gcZxxEGC8S0Q1AC3UJTggjBaADjwJFAWv/Vf8M/3j+PQBZ/4X+Zfsc+f/4I/rc+mX86v3W/cH9rPwQ/hb/l/1g+0r6S/fD84rvd+ws7mbvL/Av8PLwuvNK9F/zdPN68rfwo+yo5xriBN9Q2y7YYdgm2D7YQtY12S3heej86mHtMPKZ9V76DfrG+9cDmQsOEGQVTxroHeEibSGcItskKCMkHrsVVBD2C0YGkP5J+1j8Nv6y/fr75v7CAv8FXAaIBvEIdQpgCNMEVwSUBn4GbwX5B+cLCBAnEbMRdRXcGbMaMBfoFaUViBMWDicJRwf5AxsAOvv8+XP7DPtF+DD2/veq+av6tPq6/CUADQI9Ao0A3gDUAOD+CP2a+0H68/ci9EjxNPB375rvAu/h78HxvvLh8yDzMvOI8zTzJPIw7iXpe+UW4lLePdpq1ijVbdZa1uXXYtyr4Tboge479277Ofyi/fP+HAVPC8ELzQq8ECYZ7iAvJYAmKCbwJlQq0CmIJUMfExbTDTgKwwS3/9f60vcA+Zf7v/6v/QX+9wE2B7wJlwlUCBYGygaPBrEFIQaYBiMHhQi2C0MQvhObFRwYAhp2GyUa4hXDEiAQUQ3QCCgD0v32+AT2NvWw9VX2TPZD99f5efxi/x//mf0h/24ANQD//cj51/U29PfzzvPY84jyIfIW8yH0TfRw9LX15/f/+DP3x/RI8pHv8O0B7ADqHOeD4uzdAdt82iLZlthA2zHfQuMi5gnp2+3S9Ir7Ov8NAL0Diga+B2oMWA9nFlgcDx4PJDIoXyl4J5cj4iLLItAdnBYaDdMEHAI1/uL76/uM+pT6efy//xcELAaVBpoIzQqsC/AJ2gXjA4kEJwUXBmkGsAVvB8IKUQ85FAsXIRjoFwAYdhdHFUURgwxMCZYGyQLd/p36CPil+Er6T/vn+yH9Sv5wAGMCiQPuAxkCMABH/rf88/li9evw4+9M8ffvevAF8ZTxvPMR9MH1e/f39xT3SvaE9DzxCu8A6pPnCeb64fbf2tzI2qza4Nq43DzfA+Ib5Evnvuoh73f1GvVg9yH+CQECBJkHggs9E9wZsRslIqomDSW8JRMl8yPlIokcVxcFFfAO0Qm0Bq8CmAPcBJUCRgM0BP0EuQfbB1AHlAiUB5YE4wNUAl0AFgBa/rb+aQBGAHkBtQT7B/YKCg4HEBISNBMmEs4RyBEfEGkO8woYBksFhQSjAZv/"}},"finish_reason":null}],"usage":null,"obfuscation":"ZyTVTaEJ1gdo"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"7bTDqq"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"Gl9WkF"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"q4VDKk"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[{"index":0,"delta":{"audio":{"data":"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"}},"finish_reason":null}],"usage":null,"obfuscation":"Mhpmq34qc58"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[{"index":0,"delta":{"audio":{"data":"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u/+//73/vP+6/7z/uf+5/7v/wf/B/8D/uf/A/7r/w//B/8P/xP/A/8T/yP/P/8L/zv/H/9H/0f/U/9L/0f/U/9X/1//a/9//2v/g/93/3P/f/+H/5P/k/+j/5v/q/+3/6P/w/+z/8P/u/+//8//u//b/8P/6//D/9f/2//L/9//x//b/8f/6//b/7//1//r/+v/3//n/9P/4//b/+P/4//n/+v/1//b/8//1//T/9f/2//b/8//3//f/+v/1//z/8v/8//T//P/6//v//f/3/wEAAQADAAAA/v8CAPv/BwAFAAAABQD+/wgA/P8NAAMADgAJAAwAEQAKABEACQASAA4AEgASABMAFgARABUAGAAcABQAFQAXABQAGgAYABkAGgAXAB4AFAAdABoAHgAeABoAIAAcACAAHQAfACIAGAAfABsAIgAeAB8AIAAdACEAGQAhABsAHgAfABwAIAAXAB4AHAAfAB8AGQAgABoAIQAbAB0AIwAcACcAFwAmABkAIgAbACEAIgAgACMAHQAnABwAJgAaACoAGwAmACEAHwAlACEALAAbACkAIgAlACUAHQAuAB4AKwAdACkAIwAgACcAIgAxACEAKgAcACMAJgApAB4AJgAfAB8AHwAiABwAIAAiAB8AIQAbACIAGgAiABoAGQAaABwAHQAYAB0AFAAXABYAGAAZABUAFAASABIAFAAQABMADwAQABEADAASAAwADwALAA8ACQAHAAwABgAHAAMAAwAJAAQACQAFAAkABwAEAAcA/P8CAAIAAwACAP7////3/wIA9/8FAPv/+v/z//j/+f/x//z/7P/7//D/9P/x/+7/9P/o//D/6v/t/+r/5//r/+X/5P/n/+T/5v/e/+j/3v/j/+H/4f/i/+H/3v/b/9z/4P/b/93/2P/e/9T/3P/Z/9//2v/Z/9z/1//h/9L/3//X/93/2//a/9//2P/g/9r/4f/e/97/3P/e/9z/5f/d/+T/3v/k/+L/4P/o/+H/6f/c/+b/4//p/+L/6v/q/+f/6P/m/+r/6P/r/+j/7P/q/+v/5v/q/+j/6f/s/+v/7P/t/+z/7P/r/+n/7v/r/+//6v/w/+z/7//t/+v/7//u//D/7//0//D/8f/u//D/8f/y//P/7//1//T/8//y//T/9P/z//b/8P/0//T/8v/3//H/+v/1//r/9f/5//v/+f/6//T////4//3/+P8BAP3//f/+//r/AwD3/wMA+f8EAAAA/v8GAPv/BgD4/wYA//8FAAYA+f8GAP7/BgD8/wUAAgAAAAcAAQAJAP//CQD//wcAAAAGAAUAAwAKAAcADgAFAA4AAgAKAAkACgANAAgADgAJAA8ACgANAA0ADQASAAsAEQAMABIADwASABEADwAWABIAFAAVABYAGwAWAB0ADwAZABYAGwAdABYAHQARACEAEgAjABoAHgAYABoAHQAWACAADgAfABYAHAAXABcAHgATACAAEwAeABUAHQAcABYAHAATABsAEgAZABQAFwAXABUAGQAUABoAEwAUABIAFQAWABEAEgAOABMAEQAUABIAEAAQAAwAEQAPAA0ADwAQAA8ADAAPAA4ADwAKABEACAASAAoADQAMAAsADwABABAA//8WAAMACgAFAAgADgD+/xQA/v8TAP//DQADAAQACwD//w0A/P8NAP7/BgD//wEAAQD8/wYA+/8HAPz/BAD8////AwD//wIA+/8CAPn/AgD+//3//v/+//3//f/+//z/+//8//v/+v/2//n/+P/3//v/+P/9//n/9//6//T//P/1//f/+P/3//r/9v/9//X/+//2//j/+//6/wAA9P////X//f/0/wEA+v/+//z/+f////b/BQD2/wUA9P////f///////z//v/y/wAA8/8FAPn/+v/3//7//v/4/wIA+P/9//3//P8BAPz/BAD6/wIA+f/9//3//P8CAPz/BQD4/wYA+P8IAPn/CAD8/wAA/v/6/wQA9f8LAPj/BgD2/wUA/v8CAAMAAAADAP//BAAAAAkA/f8JAP7/CgD+/wcAAQABAAQA+/8MAPb/DQD2/woA+/8AAAcA+v8IAPb/CQD6/wcA/f///wMA9/8HAPb/BwD5/wQA/f/6/wEA+P8FAPL/AQD1//v/AAD8//7/8//+//b/+//8//z//P/4//b/+//z//r/9P/9//T/9v/1//X/9f/u//v/7f/7/+z/+P/v//T/8v/x//f/7P/7/+3/+v/q//f/7//0//X/8f/3//H/9//v//P/9P/y//X/8v/0//P/9f/0/+//8//t//L/8//2//L/8f/z/+//9v/w//f/8P/0//H/7v/3/+//+f/z//b/9v/z//X/9P/4//X/9f/2//f/9f/1//n/9P/6//j/+v/5//b/+f/z//r/8v/9//X/+P/3//j//f/z/wEA9f8DAPP////0//z/+f/6//3/9P8DAO/////v/wMA9//9//3//f8DAPr/AgD3/wIA+/8DAPr///////3/BAD//wEA/P8CAAIABQAGAAEABAD8/wEABQAFAAMABAAFAAAABAADAAIABQAGAAgAAgADAAgAAwAMAAQACwAGAAwACgAKAAoABAALAAMADgAEAA8ABAANAAkACgAKAAsADAAIABAABwAPAAwAEAAOAAwADQAKABIACgAVAA0AEQAOAA0AEwALABMACwAVAAwAFAAQABUAEQASABYADwAVABMAGwASABsADwAaAA4AGQAVABYAFwATABcAEwAWABYAGQAWABgAEwAVABEAGQAVABYAFgATABcAFgAbABEAFwAUABMAFQANABkADQAdABMAFQAYABIAHAAJAB8ACwAdAA0AFwAUABAAFAALABUACgAZAA0AGAANABQADwAPABIACQAUAAcAFAAFAA0ADgAKAAsABAAMAAcADAAOAAgACgABAAoABAAMAAUABgAHAP//DQAAAAcAAgAHAAcA+v8GAP7/BgD//wYA/v/+/wMA+P8BAPX/AAD5//r//f/7/wAA9f/6//v//P////r/+f/x//7/7v/9//b/8//w//f/9//x//v/6P/4/+z/+f/t//P/9P/o//T/4v/6/+X/8f/k//D/6//n//H/5P/w/+b/7f/n/+7/6f/r/+r/5//p/+T/6//k/+v/5v/r/+3/5f/r/+X/7P/m/+n/5v/h/+//3//y/+L/7P/i/+f/6v/f//D/4//s/+X/3//s/97/8f/j/+z/7f/g/+3/3v/z/93/7P/p/+X/8v/m/+//5v/p//P/6f/5/+j/9v/l//H/6f/w/+3/6//y/+f/+P/k//f/4v/7/+n/8v/z/+r/+//i////5P8DAOf/+v/0//H//P/q/wEA5/8DAOz/+P/w//v/9v/4//v/+f/8//r/AQD5/wgA9P8KAPP/BwD3/////P/2/wYA8P8TAPT/DgDy/wwA/P/+/wcA9P8KAPT/CgD2/wgABQAEAAUA/P8IAP7/BwD//wAABAAEAAkA/P8JAP//CgADAAgABQABAAoAAAAIAP//DAAAAAUAAwACAAYABgANAP3/BgD+/wIAAwAFAAYAAgADAAQA/P8FAP//BwD9/wAAAgD9/wEA+v8GAP3//f/5////+//7/////P/6////+v/8//f/+P/8//T/AQDz////9//8//j/7////+//AwDs/wEA9P/7//z/7//8/+v//P/s//f/8v/y//X/7v/4//H/9v/x//P/8v/y//P/8f/y//b/8f/0//T/9//2//P/8v/v//P/9//3//P/9v/y//f/9f/+//L/+P/0//P//P/4//f/+v/4//v/9f/8//z/+v/+//j/BQD3/wIA+P8CAP//+v8DAP3/CgD9/wgA/f8FAAMABgAHAAMAAgACAAIAAwAGAAgABQAGAAgACAAJAAoABQAFAA0ABgAMAAcAEAABAA4ABQAJAA4ABAAVAAEAFgADABEACgAIAA0AAgAZAAUAEwAEABEABgAHABIABgAVAAEAFQAAABAABwANAAgABgAQAAIADQD7/w4ABAAMAAoABQAKAAgACwAIAAwACAAKAAcADAAEAA8AAwAOAA4ABwAQAAIAFAAAABIABQAOAAgACAANAAUADgAAAAwABQAOAAcADAALAAkABwAEAAoABQAMAAgACgAFAAkABQAFAAcABgAFAAcABQAIAAMACQADAAYAAwAFAAIAAgAEAAcACgADAAoA//8HAAEABwAFAAUABQAEAAMA/v8EAAAA/f/8//3/AAAEAAIA///6/wMA+////////P8BAPn/AgD4/wEA/v/+////+v8CAPn//v/5//3//f/8////9v/5//n//v///wEA/f/+/////f/9//v/AQD5/wQA/v8DAP3//P8DAPv/BAD5/wYA/P8AAP7/+/8GAP3/CAD+/wQAAwAEAAkAAgALAPv/CAD9/wEAAQACAAYA//8JAAQABwADAAkABAAGAAYABQAIAAQACAABAAcABQAFAAgACQAMAAcACAAJAAoACgAIAAoACwANAA0ADAAQAAwADwAPAA8ADgAOABMAEAAVABEAFwAUABUAFwAXABkAFgAcABkAHQAdABoAHgAaAB8AHAAcAB4AGQAeABoAHwAbABsAHgAcACEAHAAfAB0AGwAbABoAHQAbABwAHAAdAB0AGwAdABgAGQAUABYAFwAVABMADwAPAAoADQAJAAkACAAGAAcAAQAFAAAAAwD7//v/+v/3//f/9v/6//X/+//y//r/9P/3//X/8//5//H/+P/w//f/8v/z//P/8P/2//T/9v/z//f/8//x//b/9P/z/+//8//0//T/8//x//D/8P/w//D/8v/y//H/8P/w//P/7v/t/+v/7v/s/+z/6v/t/+r/6//p/+j/7v/l/+j/5v/p/+n/6v/q/+f/6//m/+z/6P/r/+f/6P/s/+b/6P/k/+3/5//s/+z/6f/p/+r/7P/s/+v/6//o/+n/7P/r/+z/7f/r/+3/8P/t//D/8f/z//T/9//0//f/9f/7//D/9//0//P/+v/3/wEA+v8BAP3/AgABAPr/AQD6/wcAAgAWAAUAGQANAB0AGgAUABYABgAWAAcAAQD7//7/+v8CAAUABQAZACMAEAArACoARwAZAAUAHAD//0wAMAAZAEAANgBcAAsABQANABIAIgAfACoARgArAOH/"}},"finish_reason":null}],"usage":null,"obfuscation":"NG0tikMCKGd"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","usage":null,"obfuscation":"89lzBx"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[],"usage":{"prompt_tokens":11,"completion_tokens":16,"total_tokens":27,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"text_tokens":11,"image_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":10,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0,"text_tokens":6}},"obfuscation":"KHm3nt5OunwEka"} + + + data: {"id":"chatcmpl-DUHT94j0whwAoKxkrjJpjgOIpjRdU","object":"chat.completion.chunk","created":1776109363,"model":"gpt-4o-audio-preview-2025-06-03","service_tier":"default","system_fingerprint":"fp_6419bec501","choices":[{"index":0,"delta":{"audio":{"expires_at":1776112963}}}],"usage":null,"obfuscation":"X1JFO"} + + + data: [DONE] + + + ' + headers: + CF-Cache-Status: + - DYNAMIC + CF-Ray: + - 9ebcf49c7c870c69-YYZ + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 13 Apr 2026 19:42:43 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - braintrust-data + openai-processing-ms: + - '207' + openai-project: + - proj_vsCSXafhhByzWOThMrJcZiw9 + openai-version: + - '2020-10-01' + set-cookie: + - __cf_bm=usxSqPzRc_6vxeeef_8zcAuLrkkjkXPxoJ3szut_ao0-1776109362.6348329-1.0.1.1-K4AU_2bDYubua1k9qv4a1SHRM7X3E.hJImNU50a65Bhb9jwxX9YDtENx0cBWFrD0Un_75kmIIEkLKagOPI4zQ1zP7wOj4g2zamVWsu86UYmB4LUeml3ldul14q9vv0Pp; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Mon, 13 Apr 2026 + 20:12:43 GMT + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '20000' + x-ratelimit-limit-tokens: + - '15000000' + x-ratelimit-remaining-requests: + - '19999' + x-ratelimit-remaining-tokens: + - '14999993' + x-ratelimit-reset-requests: + - 3ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_f948053be9a24e0eb3f33fb96380c192 + status: + code: 200 + message: OK +version: 1 diff --git a/py/src/braintrust/integrations/openai/test_openai.py b/py/src/braintrust/integrations/openai/test_openai.py index f841865e..1f539717 100644 --- a/py/src/braintrust/integrations/openai/test_openai.py +++ b/py/src/braintrust/integrations/openai/test_openai.py @@ -14,6 +14,7 @@ RAW_RESPONSE_HEADER, ChatCompletionWrapper, _materialize_logged_file_input, + _process_attachments_in_chat_output, ) from braintrust.span_types import SpanTypeAttribute from braintrust.test_helpers import assert_dict_matches, init_test_logger @@ -638,6 +639,54 @@ def test_openai_chat_streaming_sync_preserves_refusal(memory_logger): assert output_choice["message"]["refusal"] == chunk_refusal +@pytest.mark.vcr +def test_openai_chat_streaming_sync_preserves_audio_attachment(memory_logger): + assert not memory_logger.pop() + + client = wrap_openai(openai.OpenAI()) + stream = client.chat.completions.create( + model="gpt-4o-audio-preview", + messages=[{"role": "user", "content": "Say exactly hello."}], + modalities=["text", "audio"], + audio={"voice": "alloy", "format": "pcm16"}, + stream=True, + stream_options={"include_usage": True}, + temperature=0, + ) + + transcript = "" + saw_audio_data = False + for chunk in stream: + chunk_dict = chunk.model_dump() + choices = chunk_dict.get("choices") or [] + if not choices: + continue + + delta_audio = (choices[0].get("delta") or {}).get("audio") + if isinstance(delta_audio, dict) and delta_audio.get("transcript"): + transcript += delta_audio["transcript"] + if isinstance(delta_audio, dict) and delta_audio.get("data"): + saw_audio_data = True + + assert saw_audio_data + assert transcript + assert "hello" in transcript.lower() + + spans = memory_logger.pop() + assert len(spans) == 1 + span = spans[0] + output_choice = span["output"][0] + message = output_choice["message"] + assert message["role"] == "assistant" + assert message["content"] is None + _assert_chat_audio_attachment( + message["audio"], + transcript=transcript, + content_type="audio/pcm", + filename="generated_audio.pcm", + ) + + @pytest.mark.vcr def test_openai_chat_with_system_prompt(memory_logger): assert not memory_logger.pop() @@ -1942,6 +1991,33 @@ def _assert_audio_output_attachment(span) -> None: assert attachment.reference["filename"].startswith("generated_speech") +def _assert_chat_audio_attachment( + audio, + *, + transcript: str, + audio_size_bytes: int | None = None, + content_type: str, + filename: str, + audio_id: str | None = None, + expires_at: int | None = None, +) -> None: + if audio_id is not None: + assert audio["id"] == audio_id + if expires_at is not None: + assert audio["expires_at"] == expires_at + assert audio["transcript"] == transcript + if audio_size_bytes is not None: + assert audio["audio_size_bytes"] == audio_size_bytes + else: + assert audio["audio_size_bytes"] > 0 + assert "data" not in audio + + attachment = audio["file"]["file_data"] + assert isinstance(attachment, Attachment) + assert attachment.reference["content_type"] == content_type + assert attachment.reference["filename"] == filename + + def _write_test_png(path: str, *, width: int = 64, height: int = 64) -> None: """Write a simple opaque red RGBA PNG without external dependencies.""" @@ -2489,3 +2565,85 @@ def test_chat_completion_streaming_none_arguments(self, memory_logger): # No spans should be generated from this unit test assert not memory_logger.pop() + + def test_chat_completion_streaming_audio_is_materialized_as_attachment(self, memory_logger): + assert not memory_logger.pop() + + all_results = [ + { + "choices": [ + { + "delta": { + "role": "assistant", + "audio": { + "id": "audio_123", + "transcript": "He", + "data": "aGU=", + }, + }, + "finish_reason": None, + } + ], + }, + { + "choices": [ + { + "delta": { + "audio": { + "transcript": "llo", + "data": "bGxv", + "expires_at": 123, + } + }, + "finish_reason": "stop", + } + ], + }, + ] + + wrapper = ChatCompletionWrapper(None, None) + result = wrapper._postprocess_streaming_results(all_results, audio_format="wav") + + message = result["output"][0]["message"] + _assert_chat_audio_attachment( + message["audio"], + audio_id="audio_123", + transcript="Hello", + expires_at=123, + audio_size_bytes=len(b"hello"), + content_type="audio/wav", + filename="generated_audio.wav", + ) + assert not memory_logger.pop() + + def test_chat_completion_non_stream_audio_is_materialized_as_attachment(self, memory_logger): + assert not memory_logger.pop() + + output = _process_attachments_in_chat_output( + [ + { + "message": { + "role": "assistant", + "audio": { + "id": "audio_456", + "transcript": "Hello", + "data": "aGVsbG8=", + "expires_at": 456, + }, + } + } + ], + audio_format="wav", + ) + + message = output[0]["message"] + _assert_chat_audio_attachment( + message["audio"], + audio_id="audio_456", + transcript="Hello", + expires_at=456, + audio_size_bytes=len(b"hello"), + content_type="audio/wav", + filename="generated_audio.wav", + ) + assert not memory_logger.pop() diff --git a/py/src/braintrust/integrations/openai/tracing.py b/py/src/braintrust/integrations/openai/tracing.py index 8d13e60b..371c57f4 100644 --- a/py/src/braintrust/integrations/openai/tracing.py +++ b/py/src/braintrust/integrations/openai/tracing.py @@ -1,6 +1,8 @@ """OpenAI-specific tracing wrappers, stream proxies, and serialization helpers.""" import abc +import base64 +import binascii import inspect import json import time @@ -9,6 +11,7 @@ from braintrust.integrations.utils import ( _extract_audio_output, + _infer_audio_mime_type, _materialize_attachment, _parse_openai_usage_metrics, _prettify_response_params, @@ -156,6 +159,72 @@ def _process_attachments_in_input(input_data: Any) -> Any: return input_data +def _get_requested_audio_format(params: dict[str, Any]) -> str | None: + audio = params.get("audio") + if not isinstance(audio, dict): + return None + + audio_format = audio.get("format") + return audio_format if isinstance(audio_format, str) else None + + +def _decode_chat_completion_audio_chunk(data: Any) -> bytes | None: + if isinstance(data, (bytes, bytearray)): + return bytes(data) + if not isinstance(data, str): + return None + + raw_data = data.partition(",")[2] if data.startswith("data:") else data + try: + return base64.b64decode(raw_data, validate=True) + except (binascii.Error, ValueError): + return None + + +def _process_chat_completion_audio(audio: Any, *, audio_format: str | None = None) -> Any: + if not isinstance(audio, dict): + return audio + + processed_audio = {key: value for key, value in audio.items() if key not in {"data", "_decoded_data"}} + audio_data = audio.get("_decoded_data", audio.get("data")) + if audio_data is None: + return processed_audio + + resolved = _materialize_attachment( + audio_data, + mime_type=_infer_audio_mime_type(None, response_format=audio_format), + prefix="generated_audio", + ) + if resolved is None: + processed_audio["data"] = audio.get("data", audio_data) + return processed_audio + + processed_audio.update( + { + "mime_type": resolved.mime_type, + "audio_size_bytes": len(resolved.attachment.data), + **resolved.multimodal_part_payload, + } + ) + return processed_audio + + +def _process_attachments_in_chat_output(output_data: Any, *, audio_format: str | None = None) -> Any: + if isinstance(output_data, list): + return [_process_attachments_in_chat_output(item, audio_format=audio_format) for item in output_data] + + if isinstance(output_data, dict): + processed = {} + for key, value in output_data.items(): + if key == "audio" and isinstance(value, dict): + processed[key] = _process_chat_completion_audio(value, audio_format=audio_format) + else: + processed[key] = _process_attachments_in_chat_output(value, audio_format=audio_format) + return processed + + return output_data + + def _is_async_callable(fn: Any) -> bool: fn = getattr(fn, "__func__", fn) # Walk the __wrapped__ chain to see through decorators (e.g. OpenAI's @@ -338,6 +407,7 @@ def __init__(self, create_fn: Callable[..., Any] | None, acreate_fn: Callable[.. def create(self, *args: Any, **kwargs: Any) -> Any: raw_requested = _raw_response_requested(kwargs) + audio_format = _get_requested_audio_format(kwargs) params = self._parse_params(kwargs) stream = kwargs.get("stream", False) @@ -371,7 +441,7 @@ def gen(): all_results.append(_try_to_dict(item)) yield item - span.log(**self._postprocess_streaming_results(all_results)) + span.log(**self._postprocess_streaming_results(all_results, audio_format=audio_format)) finally: span.end() @@ -385,7 +455,7 @@ def gen(): metrics["time_to_first_token"] = time.time() - start span.log( metrics=metrics, - output=log_response["choices"], + output=_process_attachments_in_chat_output(log_response["choices"], audio_format=audio_format), ) return create_response if (raw_requested and hasattr(create_response, "parse")) else raw_response finally: @@ -394,6 +464,7 @@ def gen(): async def acreate(self, *args: Any, **kwargs: Any) -> Any: raw_requested = _raw_response_requested(kwargs) + audio_format = _get_requested_audio_format(kwargs) params = self._parse_params(kwargs) stream = kwargs.get("stream", False) @@ -429,7 +500,7 @@ async def gen(): all_results.append(_try_to_dict(item)) yield item - span.log(**self._postprocess_streaming_results(all_results)) + span.log(**self._postprocess_streaming_results(all_results, audio_format=audio_format)) finally: span.end() @@ -444,7 +515,7 @@ async def gen(): metrics["time_to_first_token"] = time.time() - start span.log( metrics=metrics, - output=log_response["choices"], + output=_process_attachments_in_chat_output(log_response["choices"], audio_format=audio_format), ) return create_response if (raw_requested and hasattr(create_response, "parse")) else raw_response finally: @@ -472,10 +543,16 @@ def _parse_params(cls, params: dict[str, Any]) -> dict[str, Any]: ) @classmethod - def _postprocess_streaming_results(cls, all_results: list[dict[str, Any]]) -> dict[str, Any]: + def _postprocess_streaming_results( + cls, + all_results: list[dict[str, Any]], + *, + audio_format: str | None = None, + ) -> dict[str, Any]: role = None content = None refusal = None + audio: dict[str, Any] | None = None tool_calls: list[Any] | None = None finish_reason = None logprobs_content: list[Any] | None = None @@ -525,6 +602,25 @@ def _postprocess_streaming_results(cls, all_results: list[dict[str, Any]]) -> di if delta.get("refusal") is not None: refusal = (refusal or "") + delta.get("refusal") + delta_audio = delta.get("audio") + if isinstance(delta_audio, dict): + if audio is None: + audio = {} + + if delta_audio.get("id") is not None: + audio["id"] = delta_audio.get("id") + if delta_audio.get("transcript") is not None: + audio["transcript"] = (audio.get("transcript") or "") + delta_audio.get("transcript") + if delta_audio.get("data") is not None: + delta_audio_data = delta_audio.get("data") + decoded_audio = _decode_chat_completion_audio_chunk(delta_audio_data) + if decoded_audio is not None: + audio["_decoded_data"] = (audio.get("_decoded_data") or b"") + decoded_audio + else: + audio["data"] = (audio.get("data") or "") + delta_audio_data + if delta_audio.get("expires_at") is not None: + audio["expires_at"] = delta_audio.get("expires_at") + if delta.get("tool_calls") is not None: delta_tool_calls = delta.get("tool_calls") if not delta_tool_calls: @@ -553,6 +649,9 @@ def _postprocess_streaming_results(cls, all_results: list[dict[str, Any]]) -> di # pylint: disable=unsubscriptable-object tool_calls[-1]["function"]["arguments"] += args + processed_audio = ( + _process_chat_completion_audio(audio, audio_format=audio_format) if audio is not None else None + ) return { "metrics": metrics, "output": [ @@ -563,6 +662,7 @@ def _postprocess_streaming_results(cls, all_results: list[dict[str, Any]]) -> di "content": content, "tool_calls": tool_calls, **({"refusal": refusal} if refusal is not None else {}), + **({"audio": processed_audio} if processed_audio is not None else {}), }, "logprobs": ( { diff --git a/py/src/braintrust/integrations/utils.py b/py/src/braintrust/integrations/utils.py index 3af6d009..efe25109 100644 --- a/py/src/braintrust/integrations/utils.py +++ b/py/src/braintrust/integrations/utils.py @@ -350,6 +350,7 @@ def _materialize_attachment( "aac": "audio/aac", "flac": "audio/flac", "pcm": "audio/pcm", + "pcm16": "audio/pcm", }