From 2cd40289330b7b3a5cd98cbdd28f908e97949da7 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Thu, 13 Aug 2026 22:49:51 -0600 Subject: [PATCH 1/4] VCR --- .../__files/v1_messages-10af9cffed94.txt | 63 ++++ .../__files/v1_messages-c0794efcd5dc.json | 1 + .../__files/v1_messages-d7272d43310d.json | 1 + .../mappings/v1_messages-10af9cffed94.json | 53 +++ .../mappings/v1_messages-c0794efcd5dc.json | 52 +++ .../mappings/v1_messages-d7272d43310d.json | 52 +++ .../braintrust/__files/btql-9744b37774d9.json | 1 + .../braintrust/__files/btql-9ffc0f72eb30.json | 1 + .../braintrust/__files/btql-ac7f0143cfa3.json | 1 + .../braintrust/__files/btql-bce8572f7d22.json | 1 + .../mappings/btql-9744b37774d9.json | 45 +++ .../mappings/btql-9ffc0f72eb30.json | 46 +++ .../mappings/btql-ac7f0143cfa3.json | 45 +++ .../mappings/btql-bce8572f7d22.json | 46 +++ .../chat_completions-7c0db28da6b8.json | 35 ++ .../chat_completions-b5bcb38ce8df.json | 35 ++ .../__files/responses-0c37df81d2cf.json | 127 +++++++ .../openai/__files/responses-31d9a6eee624.txt | 315 ++++++++++++++++++ .../__files/responses-d93cfca55488.json | 129 +++++++ .../chat_completions-7c0db28da6b8.json | 49 +++ .../chat_completions-b5bcb38ce8df.json | 49 +++ .../mappings/responses-0c37df81d2cf.json | 48 +++ .../mappings/responses-31d9a6eee624.json | 42 +++ .../mappings/responses-d93cfca55488.json | 42 +++ 24 files changed, 1279 insertions(+) create mode 100644 test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-10af9cffed94.txt create mode 100644 test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-c0794efcd5dc.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-d7272d43310d.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-10af9cffed94.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-c0794efcd5dc.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-d7272d43310d.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9744b37774d9.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9ffc0f72eb30.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-ac7f0143cfa3.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-bce8572f7d22.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9744b37774d9.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9ffc0f72eb30.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-ac7f0143cfa3.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-bce8572f7d22.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7c0db28da6b8.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-b5bcb38ce8df.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-0c37df81d2cf.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-31d9a6eee624.txt create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-d93cfca55488.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7c0db28da6b8.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-b5bcb38ce8df.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-0c37df81d2cf.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-31d9a6eee624.json create mode 100644 test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-d93cfca55488.json diff --git a/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-10af9cffed94.txt b/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-10af9cffed94.txt new file mode 100644 index 00000000..2a33cd1e --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-10af9cffed94.txt @@ -0,0 +1,63 @@ +event: message_start +data: {"type":"message_start","message":{"model":"claude-sonnet-4-5-20250929","id":"msg_011Ce9dVbeF3NFHVbeRxVbNa","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2228,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"}} } + +event: content_block_start +data: {"type":"content_block_start","index":0,"content_block":{"type":"server_tool_use","id":"srvtoolu_01XDca4HBMnX8Wpx5pzmsCs8","name":"web_search","input":{}} } + +event: ping +data: {"type": "ping"} + +event: content_block_delta +data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""} } + +event: content_block_delta +data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\"que"} } + +event: content_block_delta +data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"ry\": \"AI ne"} } + +event: content_block_delta +data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"ws"} } + +event: content_block_delta +data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":" toda"} } + +event: content_block_delta +data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"y\"}"} } + +event: content_block_stop +data: {"type":"content_block_stop","index":0} + +event: content_block_start +data: {"type":"content_block_start","index":1,"content_block":{"type":"web_search_tool_result","tool_use_id":"srvtoolu_01XDca4HBMnX8Wpx5pzmsCs8","content":[{"type":"web_search_result","title":"AI News & Artificial Intelligence | TechCrunch","url":"https://techcrunch.com/category/artificial-intelligence/","encrypted_content":"EsIFCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDCULRKgHCYh8eDxDixoMZdO28mNuJ4bYDictIjCFBqpF8RtDusuXzS82hFrGt37PAGqNnv872QNZWPduNeCPdPrA/yjTqUiRvLj7OG8qxQQpxt1dJP1eKYYP70xDeZ7DukmY3yyRYc6lV5S7vmep+mWPWdC5JjPiHkfTAFrkVy238X9dNlQhuAg0E+N+fAHtux19ghdxELCizIlz/FGUIq2gfaF3ODKDRY95O+S+NAVBnoyZST0YlgXEVTO0WnEUXZh+jBQtSXJUwqtzGyEgNO6vCpyFfDqOXzjQhhNuZJWoK9Sq8Spy49q5WH50C1g+46gCBUwVe6DeeE/+SLavD/5qMyeJGuc4htfjYKbQ02FL1f3F6BlGtg/mibjXSwvU22UgIybUCWWOWxnPO2ObWuRUv6NlteaAx6t3ygaXGLaiMHUPCMFO3fKJ3nDk29gOrlwg+oNs9HlZbYQeB0fWoP8eCowFq2vSkMZU3hKQTV1k+n0lAqgI5QmEy6gwUMwcFXBUnk7swwtOZnfM6JEHDK70MU4eY+QuvmLnTPG9sa3iiklXyUnct0F/v71JO6fjbL9CTqpyQV5sU3s0xEmF46hmmAX8yH4v/GjiLWi4H5rU+ks881IiSISZhYX68qvpf88X6yd0lBkvx2WaJFJYXdlB4hif7H0yT5t/vKnyXhfp2kgsACAjO2eUffYeJbwp3ZNZLJKgtD4JUxkzSgRVvwSzXRhsdBIgCZnNUPrsCw4jI79ap2F55JKgGZkMKBHa//vrUybZtwK2JlvDXTgzH3tD/iCChMu1TVJiH3z7nYCm0shwHpyhWdN+xVoGbKtZw3NpzRlsn9+bfxbk9AZaAlYnwgdVdVpbRkGs51JlRiF7TIFmvBgD","page_age":"4 days ago"},{"type":"web_search_result","title":"AI News | Latest News | Insights Powering AI-Driven Business Growth","url":"https://www.artificialintelligence-news.com/","encrypted_content":"Et8JCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDIRpGLw2fKdB5MmthRoM3ip6Pb8nCU+a8I/fIjCHGoROM1mDGYQIzi2OkelB0d3H5kxEKiwx6q+6INXBB37tp7OyauYHkKQruUoNm2Iq4ghx8R9b/auJqYGf4m8Eh23ELEIc7GYPfNx7MBEfruFMiTB83xYoME4SNegQgwxM3uSJqFs+F+lslRPo+zLPgIhll43lAlcKg1HzD/DfX69izjjQFyEC/08Liz3polYev/ddcrceaOVE3YZR2mvNZHuLwujlpsF2tidQUQet1ESNhz2HSal6HCemdAvtuj47vXQMb2liqLfu6y/oMvEIVTBwVIT5mdmQoLOylERL1rYP2p2fIh0cQwfVvyE480nClJehazM9fFrDB6oSCRHzY/5JiiP9El8Q1fo+3yC/QOjjvLzRc0OcTVEAQWepcs+XX4QvY7z4HYBBR7x/iAfbD0IjWz96b2YbenIEuFOdVFdfqLB46qZpkDqjVdalnXYNh6h6vOjSsPu078TMZuypKG5+kx9+zS2Ux2lCRh8rfs0xVXbzvKio759E+bmcJtr0qhuOdp5h6yCEl2qFoS2YttNigsAOWbSMASof3/pcVNhoYtke7h1qvScuO6i6NIjq7+F/vfygVkmiEr3wpgg4LFsJpLynV0lc4oVqQ29XY+VR1poahOZQodEsdtISjvMkd4Coxj7oD4RsDCrmksrVG8xMtqKkckQ7jwTsLcmR7CLVa/nR2bPqVs/BsFlIbSjPTsWIAg4qK1E8+fSRqE3rFAq5AbpVuosSGAf+ggW+2kSImkCh0+uedqTK8WUg3G2Ahlqr3ifyB9VCDCeSTZFPgUs+i3YBtZvqGVyRPaqBTqPlXxwGRGJhWNUoX5h77UdWOv0PUa1nP97FOR+eq8cQ2udGkwtkvKv5Upe4cOC+lW0v/38UBpec8UVt/FsIPJZTajxtBedpj2rRzbIJuOmlq931f8W1nWwwKivP9qvbQU8SlcPfvnWDmF3p3Ld+sWg24kmVaODk9f7OLLafCSWKUwZdpoKtim8K/lL9W3wbnfBaVWoS1jYFMRtKi4R3LmFjtw82m24UiAp3BJntAZkCbtMWEmDb2iojdAi1FT0JpM/W7Q7MzwaIGjxcWXk0xex8YCdRuTprsgMRZ44hG3o3dj5xTHBmKCuMEWGg7MIsY3IewZlyF3hF6po9yeJkjxTl250s63pAX4vMAG7AbkF02m96tonoBvUL5bNfH5rV7G8X0Nn+TwhFe+TP9Q8XJ3gufPk/LMKFZFASzlZqmrMqFXN6Uju6WKTjYh1czmUrQUPPkteeznkEu/s+wUeybQAb54wsL/xhxZMEdDlGSdhHVpUtyW8xNZgnsxyYnTidJoK+0HYSyZHhDC5ucZa9DMNxoKXQYejPsFxEVzbtqeL30hd3xW5XvdIinKKQdvNBsQ7ZzMVgkQOzRWYrY8x5vvT0nqRkeirsDhJU3/2Qov7NzVXac2KTW1Dxs2CTSDcDeG//sqxC1bC9dP+RKv1kKHs1+EDhtU7WTIjY29s0j2+1T4TAaMx75vFiC3luDXcnSJeQs0gmaOZzhuLuYs17cG8f1MCddELibqtkL8+n/+4O2j30SioYAw==","page_age":"13 hours ago"},{"type":"web_search_result","title":"AI News — Today's Top Stories, Tracked & Ranked | AI Weekly","url":"https://aiweekly.co/","encrypted_content":"EuAQCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDH44T18RqE7AYmzmxRoMWKQVmTMyVqysrfbkIjCsqm2bWW5++dc5PWREWdx+aR2SxDJ9DOvGP+JHey1OifSPYJduQrEAr0lbJ4wCkYAq4w/8qy4Z7jS+e+iidWUwExfYec7m7lWYDISvotOvhgydJWEX9g6lZ2abvKRbYSRFJnRkzXNLcp1oThsYensvVql+9W9mJBFfQ/FzEpNbCJvwEJUzHYcLnFhQFGCPTVDxARPs1eBg3hc5frWY3qS2PGLO1AEykQVpDpaAz1SUONA8tQ028JCHxEAG7FYDWhQI4O12d8ya6ILXzANgJw+0KIB/ZpEDg0LnnVuyspXCFm8xuMdCVyygKK3QFkzfZcCWLWd3MNaIj+K6+6/IkZ7jRLncUBo2CuukGetP4qGG70K58wyZev6AvUj8/BgwBO7m/UhLSvC0W6UXXX7yIty4FD2NjyyLe/rsZcpI9w9eMf3OkdUAGCidtApQCB2RPd0xn2FrMakNEa4JCKDLBO5DrMS8OxXf38Fpd+v5VSZrblYrJnQ6HSJdUEOj8lPk5s4GU/YC5uO9tKxScs+vVTAFPyvCtNDtCuZgT8v9tWu3y3SiqL70bulPjTEbSeG4MwOK/NdCrWm0zAqHmCN+cMeFzZpbDzp50Gx+ksyBaZDPJCoyvpdmmPwqVW9/7hq8e+nUZRir6f/DlmaquyQmEB9TYrcAYjAgARasOoNG3L+ZHslCezgq8m7U77vY+RpsK6EUl+3jUU6FO8uRX5NwSUWSPF2QSDcEHjTRd6JiuGBNLODwf7geAOIsoZscyrVopv1/MHjUvp2QKCGgx2pUt49cWXOXlmVVHALuzi8ZVevPeRjCcKwlWG/EavYKGXuybRcpQJdzr+zP8OfeZo1a3MRgOPMQiAV2fpnce77K4oakkvRX3fVu9x313CUddO+55bVWqS8/288BRKctuLZbK6jFsoeSywRInDcOEQ3PvRJ0gpgHiHC/WvWQQoKzRoa4ikg+N/1B674Tdhwhcxz4KC28PH06QBx1WaV1otPqCbtDHNd9G66iyO23rCxF4dE9t75uR9OAJ3sZ4twdIGPdFI2xMlLC17tbJCFY0BQwyeLjBUdsJPL+GvG3JVXb0cVGdVBO/2nEsoZ+QHrrRuKMhjFJj4crcZydPa5jdRqcpoH4F3b44jS2EvSIAYqDZulZ7D6J4NLMUBv9QpqHiQTnMIxjRgcipV33VA+y2NERoe54pP4ciy8mxtFvoNuiUPmmwF20XCfAofJQ5cIxu95MPGOVlWWYJWogPaliEBa8TXs5EdkyVZHKxABIha6j1D0HRl2ADEEJF6BOUqRXeJEcyz1oQB/Ff40NLkB/uY+i4IEnDOpBUdSMoAhNknx49nXT+Y0I47OIbxRCmaYpRFvuhxOfGF4Dzq+n2aXvJMijpM8GGMYLaGp1XDVJd28msQeC94NVqEanZQxiPQ81Mg2Kws+9qs+Wr3lm7kkHvc1ngjCQ9fxaqOcFMfuGZUT6ldXByj9nWHz2cLR8RQHz8/ZHMRwcGqo8DdiWjZgII+XNCRK5PKLG4m8PSyHNJyCTiQxtbWdAYrB+tAuP2JgLcgGhea/6mO1tZ/uaHamaUEHoFfyVBXZZJb5Ak4ybePRJOD9TasdIEKGaIiVsrEM/IKmVqy6/y1nij4cBAAFVzgx0N6zgkPjqcCLpvafKkK08YicUxjZkyfOkLVfoYXB5Hi4UazVXhyAtNOX+L3L3mUtQu0ijKABi1wA/YVvLLtgdcZvDDyg0TL2hWXbf7I0ZheFSHdt5S6totWIOAppVlpxB1IAXuZ7c72B/aKYFXxpSrCykJkKNMK9+gjGUpPlVNoLGZfrBs3U2zXeLf+Jb7fIrlMg3zPkxsTLAedymc158/t/8pGZUWeGGzJqDet9ApSBvjjEEmGVdl58J1R0x5N4MXe6bHcjg/CAmyGs7nzP1sjXLpKcuPCXFWiKukMwuJ5Pj1a+8I33HlDHVpqG8IccgM8cXJMpCU9YGzr74TPca8RqTgxae8i7l8mPod0FbNa9U3U6wdZ02Nm+rFI0ZJapzV99/putKpFL/g97RZTzhFVYVR2vhlx4uusDCx/EYzhSHml+bvbiN+zzZhmitTuR8ruGeEZ6JhLxhRtP9eDBz37KJHQf0BpYuN7x061+lSTftcls01neCXZOG1JlVmpp22gD/Q8K1KZ4qd7X1wRrI7j9QCdaRk1azFY3UBw6/t7IJP/otO8GGlVt6ZESP7q318nnvQiFiZgvntlK2i0UNP7KoZbEqNmDj7mWdoF5FgJfmqmH+Gne4ip4SVNqlsIdlygjRRVAZAggrsgb0WmivYVk9EeV/oTHEf8gvGmFivN8swcJ+7KjHXshm/G/dcc+t8qBUBg5ITeQg15HB7BOtWgPWwPfKWasoV8GbhxXWYy23OcuDe3Wii4xryMQyqMUHfM8c/p4m1aguCL8bvQ1Xya5NDEZRo0CCvy09xJ+3qOsAyLDoT/jqTn8SW5PwOkk08C88LAsKhomEpIlrUcyqujsufC5c34E2xuYr23FNDqY6hF/oYKnYtgoyDmUbzLYjKilSjK+ZaLOtc+LJM4RhQfF0+3UsjFgXnZMzrtqLdOAO7lUAXreHJj1cvI0BfwT8wqoJUyso7b2n0mya7lMOJ+5uJBYhDcQRrU7b3tnef3Bai5FhLlNQ0BbSyRFwgkJyGSN7pyw0GIJ8m11HCsJkMjGMJ5juSYQAFwz83DJ4SnNhtHE3DIX9IQPSSZNZ/E2UbEho+IQ5WdK/kVnwxur2Dw4QO1H3B4UIoa8YAw==","page_age":"12 hours ago"},{"type":"web_search_result","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","url":"https://aiweekly.co/ai-news-today","encrypted_content":"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","page_age":"1 day ago"},{"type":"web_search_result","title":"Artificial Intelligence News -- ScienceDaily","url":"https://www.sciencedaily.com/news/computers_math/artificial_intelligence/","encrypted_content":"EuAQCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDDfQekYbso3A5GxsRhoMgaFrOie6f9y0mECBIjBNrm9CPhceWjMhQ5hCXM1Xm/JfYjw9HPgGgLc5lz1frOEFgxY6cXVgL6gs09YEHtQq4w+ph1mkcARTla9OSQSfVvnf8WumKMsmpQTtGRPeEEXv2WVTrTFvEcxg1BzjHyV+3P+C6Gk5ySeAIap5ktjenWKv2luOnd8D7M2P0y80xqH9aEqnKS/Z4Abss0M4Lb8667NEORqdqBpfNyOldH5w71KWclMsqn/vQLJG5cpi2DVNXyXkeUyFV5H6KWexcH1qcD2avBoAL109q9e8BP5G8stoZeVcb1Ys2kY3t01s3M2nT6lGQUgF3XG716dJ40RuInrXE9rG/l5mdAe9kLPd6JFfEHOzn178PngNeiLobKoiTckwK8A0/cKQeXFHQA3TyOt+nKCOcZDYdwfnXzZxBpqVgQe11mgVJsfOZggqWk1Hil8JqRmmeTjSQX9a68LdoAF3a/2LOujNQhixSEpyi1iSgtCTiKE2vIebVFx/5U5DxhGhBMoGkk0UO9VdFCRiAKI6ySxpUYvZpAXR5XhsPw0+2ECYhn12WdmnoxmwBQqIcqK+xNZ+epKR6/xiTt9OFSoMGVgYoeZgyeoRvE0WgRsnGDL9okrc4EEMQ1XI1dvErwrm1CtrqaTI7EYz844CCDWfLRNurnhXB9SsTmoe+u/p135JhgnWRswKvM640LbLIB0+1fAZYVNgYp0gj5/WTUv8cdTg0r/pawEYnil+9c0xOoNYe/mba+uREVuDXtgV2v+3Xdy6OJP/guL2Mq8kSpxCEVWO6inoM9yLplkicjz9uvgPxvTr+CTAHgzxHVCGcdGwuQcTVrvF9wsLO0SdVJlnnH4XipAGeUgu3JWhvUoEfcVttHxXxwLsRajs5S6Pswk+UJE6zBZCqwgUo6PbO0gh11RF4W4uPQ+lEWzTh20Etir4EZK59WhyYIGrlsHr+sccTZoOHTQEXVjKns248N60f8OtqBRIlfK71xT7KXt84VXy73qnjDfpn2sIZWOEo5l2Pwbt5qkYpScvMbbCvjv8ourEoqSmB1mK8iEcbQaPZez2jAnGNPG5gIakWyzzL6MJco5ooK9lx4tq4qqFpYNINXjZq/7utzfdOTMUNG/ZYjjqkulbSlifdS+nAyD0d1ffaVyMWhIeeDQtcA2JdWdsC6Hf/0/vXdosWMJyubyHeqLVo9ZNvTNrxgylno1f2Ptwj+XQGTZNu6n6OXGEFGypMgGntSNOLRSmtN+A1QoO3TJcg7XCo283d2GhckM8vl7NG3GKoxOmFCUwdItVlpTJKpPD9N1LS3+UXlX1xIzgcLBNDBs68EdmbT9+SLxgzGTf2mnkFZd9b+NRSqR36qf7s2/OJPuVu+f6HYgwiClpyfUn2b0yrrVt4Xhkb4HlhljwgGPq3rCQGDGqNAkT5xCtC9HQfjJkXV14SxwQvE2DuuYFYttuuGGY6ZBPojFfqFxlaVGyVyfjCs6C92SFvVvvjk5CdhyCvnVai248NtryKn2HsT1UeY1NFb5B0YhEVWB9cwuqJ8dBKhrqXZ9ZUwlihx7+XofN5IIsaLvNhanZL9WthCBa+jHbrttIBap9mTr3K7Co3EBa7WaXW55RjZQfy7h6E/IP23z+2lxbDDixoszts1kMuxYSrsVBbL1ENhuZ1N4SEyLJfCuPhKF/2Rs6pzB7MJ6VCiq3T2bpZZLMJM6A/rMXCiZG23an9NNzjuIK/p13hdH+iujAPbN8tInImh0qIur7oV+EpAKkHPBs5hrxlPyyIUC8+8DGawLlr+eH/cMZ/H3VzncMQIujIMduTaonEQh1y6ZCTpAPJR1tDGD7QqwehX+LWHuUVuWidzq/Y99EP8tKlXeCz6WU/6h2Fau5WxHTX5zhwzWcrbrOcWe4urqHoXZNzwu4Z94ckrYbofXcQU4WRVkmk+oaLReJElXbsg96TdiT0A356kp3BoVgBGKomd7CKHrIOkNJwCbR6kDf9z1Of8s4BPFlIkQtBjZNleoQTZs3i2nhnqY1iQJi5KXm0rmWwuuiqJvWBMg6jWtcUvAez+bA8bJ50iDarnh0D3KeIBmeYEfRLCyL1rOb9CaadsKSkZy8XNv1aLRWvsCrzcBLhsCOWMC8eR6qsLpc0BEvRskWvTatA/tzDEOJ9xFf8p5nggUdkIWBmy+jwWlJW5AFze21+ss4fhDkl+a7lyPCfIu7YIb4KRnB1u2b0rSStEhdFEc2arLVW1mmTjty8s4K4A4VyOVPbwuYlRixfWIZwCk+vXKaNUYk4JCZZ5bUffGlAp3qfEaEIcBzigPcpegXJLBsNgyL6u7XkX6zHRHdBcHFN6VoZG4KPbexs4Cls81OPNN6qLoB6aDoERNSr3SSbmzmjtE53AcZRvd73fcuTxiXIpmylpC6BllQIoRXuL//7GRxnnVxnylac/oRJBOvyHJ79u3bQYspQc5N9pFqTAJ0qtYZpW+SPrCgjhSHWBzokbsJ+VORX5QfIRTbOOqKRDBT4Blv3AX2oJbClbSMfVz4p6rHu3RyW5iq3ZZQtbU74mgqCcPJEW3XMRWn/8NW5/DCXA9T3tulwcUfBznp4t1Md5zFntKXa6/BbcWeybuB+zyVkQ06KcS9lqcmD34yAPKAE5axhylikMnHWNm9uus9+NRUPHaZY5gBCpPaLMKOvYflpuz/1a42Hg2FQ4ZilKuVPByX3mrq0Rx81qswnljx6MoezwxX6o2fdhuQm0Sgzt2BNhuBT2TD6/hkINLdIAbdRmg81afGNnkYAw==","page_age":"14 hours ago"},{"type":"web_search_result","title":"Palantir Leads AI Data Deal With USA Today Sparking A Newsroom Revolt","url":"https://www.forbes.com/sites/sandycarter/2026/08/15/palantir-leads-ai-data-deal-with-usa-today-sparking-a-newsroom-revolt/","encrypted_content":"EuMECioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDJCgCv6/31rrBb6tgBoMasq0gBkoLMBPg8CuIjBKZXRWe+p2aXgc92v1Xifata5k4/etC+/1S6GsYA4v6g6a1YrVZAINkJZmnxPFmckq5gNHkWqr/2pbKi5/3/cWskiKutHBnwjlNzVg3t5j3XGuhxd1WuWCIYqdaed5gg61XAr70myvzJKUTe8QSa5/C7DqMq7xFGsdIFXZww/OckRu2bZ7tRP8gjBKY2Ef4GB9j/Cw601m5P6f3wXs9bG8AJnFw6Om4wjxAPCOKIRs7eKVip2r/VvfPv1NhpuP3r1tf2I7oaKFmY/QRK+ojsdthJUKqLk16fFEgsBq7KuIV8HowfelbFFI+MYIaUPr1j8eVvDnZD0KR3zQtySbRAOZ8kXDthFpcuF/kZKh67fmhDYkwGf+jzhiILsPzZxWezl6UQwzPAjYV+MVQtBeaMAum9i9IIe+8v4ZmzIJz/PMwfm7ci3U6UA2n8ZvBXKNZh9mQdbeyuW+mgguu4XtIYqBqJlfEJoFz7pvCUhGl3R5ZZKOak80YHJDldcBiAi+6HESGH/qh8YN/VYk7pnk2Y6p3QyAKr/mLYZ9gkZRtkTrtIuWqyZac+OAQSp81quwMg5uKlEO3dTNwKmoGdMpnP0U/To0Q88wmTlNGpDPRpL6TfKCG8Q+u6ZQhOmdJtnRVUPTioRPlDIQpYzL9PDUsQ82Y/JHAwEjiJFEimVvcHCRBBh/ULKmzHvsFDufSGlqhTMNkLNGKT2oDkcYAw==","page_age":null},{"type":"web_search_result","title":"The Latest AI News, Insights and Reports | AI Magazine","url":"https://aimagazine.com/","encrypted_content":"EtILCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDEXqytgVdGQrzKYQZxoMNRLjHNYeBf/7Ny5KIjBqvvbPhc5cF0ehHN3v2hA867BluKiacjuFMikJqkFCIeSZKMRJsgrB0PKtuPQpSiMq1Qph8qq5ASOiiySW23/p4Cw6UUcWGmNome3owUWMpAo91N2WgD0OEG/bHKsZjeAXbXRQroDhJOwZYuuf0HNJhOusHkohqe7E7Y1C+kmiLTE9B4kbpGUDlkgvlxoK5UP6fQfMt2Xc/iw0db6H2hjVfS9zdR8/HZM24ZVSbx+RobVJaqHqXds/ef6wrZJdMRTw4MhGQhFY7Ca1ca6OUT/eXf/KEo42+wggUqtwE6bYO+Vud6f2ZpFsFMVU8OcotGvYfkKg9o0Iv+UgWP5tLS5QS0yYe088P7XMzSxsjyIudeE7qQA/qqWB+jQAdVSB9tD2/QIc8E0DEmzDQmt/o0zVZUZDqD7zHCnrKbb/B1C/nLrpL0H12FEdJPB5IiX+iz4JAZ4QuZuUK704Z17FNkshplKB1UADXhkj7kuwbtob/h1jj8O7UjFOvZ0Czy9TJdnQzdXDTn7dF/i5OcmzzJZsoO7Szcc/+cjdDzuTxZ+BtcP936bCX7n7VE4jbjvlVLNfV0BhuGfe7jMfNIo56rNPHQb6IJMv6S3Vq2nEJDxfCQQvMvg3r7Gm6juzzQHrm3VDvxKMPgjkJfmYGwju37g6YzOjBpww6lYMJhb5COX3Wz0XxePLMwKAGF2VDhiNzeTWT7j+UO1pA0+IDPMElSFrxEmU9LKGs2DU+/Jz8c0UM7KjfxLM69oSTqeF8Y/1SXJTCGe7hgrlMAj7RPh0MMuzoKAgP4N8asXEF83oYt/lGVgBOJ7NfG6jAGMtsZ5zVZALNQWXPz+DdtneVKDZ84MZkS4E6yA5o2KAV7DB3CN41SIsL5YFPzRVeN4exIZKMtCrDw1NsvW7m9iLGvQwMZ8EaAIlMmO/Lk1TXVyYjUIHLb+ilDHETVJoqt0Zu1nYDn0lnPXfrVVOPlgDy4w+B3AwSHNRXCNJa4hukhG3TIMEq264kdrVGV1jpW2IytPz21yLolBsC8S/IQ4X30pm0y7i827rN9VJcIcmXnawf90zp4tcgcyVTc5ZiJx1kelbjyNAF5rARtJ3H1K+HUBZg5ObxWgfDQUkQd6Eudowm6qvaD+ZWy9GdLB/7DNvYW12WVh3tMYVd5m1uLHEMMk1T97jz0ZSXdl/pa3C8kYHM3uhdBTG6IFKkulabw4iQbHXVaYfY8o8+vavp9dN+t3J26Ic+RW3VJiBsb8QEU+Hb2nrJzoUyrgopd6khpCkwvonvRp0T+rf5iv0yJLWwEZ5030rMnbxQuAPiRlXsmegBNsvkpLHybpaAeKsgpVkfU65Cyrnmo/+ThJ++V+Bo0VuidMkk0bi00oi7w0RY1pQu/j4Arxjtii9tN0ZVeiiUZWy4xI7lemUsl9j168p7UVk3w6iCKU9DOLNuvmuFBNg2YGc+awvnTGUb+SJMOtwP0WhayYnx4336eXANYnTiL0swLDs/sXjkqLdT9rAXvaOyccm5F20vY+y1j0KvNIz0T/W/MjtYbiDobwbn4H5xobTGwooL7v8+ESbtwV+uV74rmOeNI0ZgLGY8MXrXSCA8+ug+DguzR+4ZpVD6tZh8PvMgjMIvirfREF1anVOmxBP9wyiSRPLt7iruxo4vGE7wGKJ+HFn41mylf8kbgmMUt+888MTo89E5hJNBA1OVYSHhW/LGQbJ1JJrxZpNViNvTVSwfh4QgBL5xhRQjJF0EXZhZ8gHH6RiKi2eLswTZ10iYGI7x6mLYKg1hUq2g1o2V4T7BUslsqvmMDI+CT4PU+fp66uvwNqVx3fkOYJpPh44b+fh+V/jcJ8EZBoI45yZfKPqiFLg05+0MVq5Xn73hmB/rQnG8fjvnde8IuEYAw==","page_age":"5 days ago"}],"caller":{"type":"direct"}} } + +event: content_block_stop +data: {"type":"content_block_stop","index":1 } + +event: content_block_start +data: {"type":"content_block_start","index":2,"content_block":{"citations":[],"type":"text","text":""} } + +event: content_block_delta +data: {"type":"content_block_delta","index":2,"delta":{"type":"citations_delta","citation":{"type":"web_search_result_location","cited_text":"● LIVE Updated 0m ago · Edited by Alexis · Daily editions · About the index · Bloomberg and TechCrunch reported Aug 16 that Stripe has finalized a dea...","url":"https://aiweekly.co/ai-news-today","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","encrypted_index":"Eo8BCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDHRYrNv53GXJdPHT+RoMMP3GcqTFYc/JoXbJIjA34NCiIgFWxWOdjhTBOEb6scgUqtXdpMXnxftCyQdXkURpeobc1unZEcCzxIxU4oMqE8P10649e/tjP2VuCzEXW5H9+swYBA=="}} } + +event: content_block_delta +data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":"Stripe"} } + +event: content_block_delta +data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":" has finalized a deal to acquire OpenRouter for more than $7 billion, representing a more than 5"} } + +event: content_block_delta +data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":"x markup from the AI gateway's $1.3 billion Series B valuation in May"}} + +event: content_block_delta +data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":" 2026."} } + +event: content_block_stop +data: {"type":"content_block_stop","index":2 } + +event: message_delta +data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null,"stop_details":null},"usage":{"input_tokens":8685,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":122,"server_tool_use":{"web_search_requests":1,"web_fetch_requests":0}} } + +event: message_stop +data: {"type":"message_stop" } + diff --git a/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-c0794efcd5dc.json b/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-c0794efcd5dc.json new file mode 100644 index 00000000..9320fdd3 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-c0794efcd5dc.json @@ -0,0 +1 @@ +{"model":"claude-sonnet-4-5-20250929","id":"msg_011Ce9dVC8dLNz5F3yPgsgYc","type":"message","role":"assistant","content":[{"type":"server_tool_use","id":"srvtoolu_01C6BYUKEhPMDYrouKReoiVE","name":"web_search","input":{"query":"AI news today"}},{"type":"web_search_tool_result","tool_use_id":"srvtoolu_01C6BYUKEhPMDYrouKReoiVE","content":[{"type":"web_search_result","title":"AI News & Artificial Intelligence | TechCrunch","url":"https://techcrunch.com/category/artificial-intelligence/","encrypted_content":"EsIFCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDJmuJweZyIRcsWcL1xoMfWghxklulCi1HNM5IjDBXFkuXRuEUfeDKTJvcIWxuap5El+yfmL7b0IZnT6FMX3XcwroGIV7X2nMm3gv9L0qxQTnuFncYwlVulqQNT8iMcFMdHj7GTC7xaVgwcw38HQbnMPOFeMLmI7FurEVPGOzOn8Y9JMagEcuoKN57CnfBjvwvCspk4M812lec32zYKjg0g574pkpsCAH207av4LrbtHUpLFIPsj0Qspm8cGCs7nCPfHKEcVg3Dt3NcATTZXziq8JZkUjHasHL1djAatF+uSe3dOcQzkNhki9AkRb+1VPvLazyGP1DJwL+jVg2yeS+6gYXXmWq2/0kBYMen/1FgWgHzWmYYIVnrtouU2WWXZYIm/3WhBzIoXFdlMOEiclilcmyn0KocNsrNs0Pl7Mx58+nOXP8tDAxsHhQjT2J/+aVxJczXKk2x05n2X/j25/zWwtXL48B2fD1zHNvp+2mBZePmvRD2HHz98kkTQMuNkMyBC8zBrs3gFr0KQXP5Q3tK1QlrSxcLNOmH1lUVPHWn/S+zWd367E1Bbsp765MvWnpboIJfCtVQX9u3sqZEsT/4Qza3NiXm5xwdxKrge01y6VaRNLuQbOVYp+JeygukFlcA8/58P5yYvtpYOR4le+brp9hNlm9XJjQJady5+ndmd0QU9xSyJmVoQswmt7SDZV0kvKObMd5Au7ZHwCMsm1fsXrx1H+Svr5D8aNXDpA+ZTPnY5/Jgh4Lbjaydzv98j+GybxFBUVBY2XlfO11BvUXJLWOJZlN/PDdpEhG+MA+qpTVk1Ov89mfHFxXEfOBCpK7XTdEIbbOZwlBXgI3oeEQtgjZiCPJu6LNJV32LwgjLyMwJE+hxgD","page_age":"4 days ago"},{"type":"web_search_result","title":"AI News | Latest News | Insights Powering AI-Driven Business Growth","url":"https://www.artificialintelligence-news.com/","encrypted_content":"Et8JCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDFvGlDPeHL/8GRYxPRoMrYSWJ5yz50Es49F9IjCyGY6xHXgzfwp+QCKGlxaVq2IJVPrXX3+P7nn72vt/hkMbwfOrF6CNa9EMA1P7HS8q4ggp6WyqgJoe3KjNHBFF+TzEpv1KvWywunJEvmFHYndyiXydFvcoMBVs2as4PovGGPHsJNNJlGmogbJDqi3LANvt2zGgRy19ZuqtGAFCu+xLBOQnKWr/kX+1NnE0uCMyf+VjqqWbgtKtpqFu3CZdcs2gMcoloXIpniwhIfKeQI1YGWu2ElPwipeCR6HE6KKPwFQuic5TvPpBbb6K19BGDGPLdz2VDX73sXXKT7r4YIR4SzpbzTyxiFaVL9YTsa9YpaEXQMbEegDFgisSAz/GknBoqOEIJAkhGr7Xj/ybY8wHOmzmY+eEmijMZpDYjRg+y92JrySBu+73em4geLbvpxmbM3OYfpSOvpZDTliljoU4NmSmOcuZ+QnsMgzXos31QtGxAqjCt9RDcDu72LojwhhiuQ8YMj7HcZEWCKBosfwQpJSVEjIta0WxM7Ibg3BX8dlrWpogY5Mz9/1XHMTs/jWUWF4htec0qV5IdlEMGHR6Lmu/ikeVGiaBvw3EQg9u+vK+PtK0A1grta2jujfhV/Wm02sp1n6243lRfPuh9I5TJVoJyY+AnAY/Eh11ljEvcAkJlGkQPYAbHk67MH1TK+EVj+vhY4lMZUJWBksIG1vS6LncoyeVJsiOchgZJYz2w5Sf0KTrMRNbFmr+kyG8eTDkGyowONoj2j9FooePXiRnd1zh/e59MockhjvhxNCzT7/kcwCIThbfVoOR0qjZ7j0jNtu5O7i0Czt+GMaRD9mMNANEPBpFZZa5Y/3L7mCxnFxKPxHdZrTkC3aI/OSAVK6NHsvEVIO1I4E9SbxYib20clcAF1IDrPHxZZNfXlEkFqSYN8lahn/+qvlt1NPRTJUhsY4oOrsSZPz3GWoA2vzBIg9PBj+sZ0nh4+7D7B2Btjau48/pj5OaKTjftQNaza3wrn6LRY3r/dCxFzqNYAIbqo6a6XR2yDUC/B5GfwgOEcqf+PUb1VGqHLzw3UxRoxPSUXa2SyhdGGQbkkRRBzF/IwxitRC8pd+hPl0VA0VP5h/5821HZKJavr4jO4A1eXl0s7cd+6ZiF5uxHe09GRvSnv6pZ86UYxi5+ppRsPX4o+4AOD3c0rm0+MkJlhMdtR6aOvvxN5RtBVWiRYpWIVRhJ51DsvRprd5Rkadti/2muE/nteoLzqlKJLJKQ8jRdFLBPb5hhgTavw9jj9SObnjyFIl2Jxxb5plorlDaQ4N3v+GAGbSttKg5pFZvmgSWZFe/NXNQ1PI1vVJEV4AaGdRBSFUy1eQigH39+3aWKbF20VUo+d3bLoR/GpuLe3WCcPQ5qroAS4uvFOmAW1Xvp9M/ifCeX4vY+iur8B6jUxprHTQydIpnNsROMrSw+TGJMih1yGO3VyXpqdhgaqCOJspiv1bHphjYuDmHdquyrRjgSbmrmao9CQkfIQrL802rzVThWLPzSmha49+TUu579RzCa20U6+FMtRzEezKk3ab/ynGjzsyvNLMEqAPPH4hr/a8gAi4YAw==","page_age":"13 hours ago"},{"type":"web_search_result","title":"AI News — Today's Top Stories, Tracked & Ranked | AI Weekly","url":"https://aiweekly.co/","encrypted_content":"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","page_age":"12 hours ago"},{"type":"web_search_result","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","url":"https://aiweekly.co/ai-news-today","encrypted_content":"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","page_age":"1 day ago"},{"type":"web_search_result","title":"Artificial Intelligence News -- ScienceDaily","url":"https://www.sciencedaily.com/news/computers_math/artificial_intelligence/","encrypted_content":"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","page_age":"14 hours ago"},{"type":"web_search_result","title":"Palantir Leads AI Data Deal With USA Today Sparking A Newsroom Revolt","url":"https://www.forbes.com/sites/sandycarter/2026/08/15/palantir-leads-ai-data-deal-with-usa-today-sparking-a-newsroom-revolt/","encrypted_content":"EuMECioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDEFeFEaVH90V/+o4tRoM7G/szdBpkYP6lttDIjApnqZvLk3CJHOD7eV/wgTHFLCQFG12NlNzgrG4A0NTtqj1ldFa/1qR8pB1uzy5jk8q5gN4JHgHxl/LACuvOry3qmKE/NbGBx5cHNnmIwbOSBGLqpNINd6pNdlvsvQmzFwzxsJVMttoC23hWxZoIAuDAjAsgZNwAoF2Hw56vSOBdCkCzSvubznGsc8vjbK3RyQYkJXeLqHFOUlXMrYAXiia4fWeeNwSE70etLY2pOYRhsLEhrRSNY+2YjCWg7qimTtze+pYpwbT3VjjScmw3FdFcPhWUdIp57aIl9D1BvlL8WmKRRM8HGxsADydifiapfiKZAdcR4ayVzJ6T58Kvk2HPbaOSTA05CjXED83OJtUGzBST9tlUAhHru5i+addiSv4Kq56Q5jv+fx0e2BPPzpUoDKP34neAhQyUalvqDV6tO62gih12comRN7Wtl+gBkz6BKJU6rSpU0guYixOFu9YrxOtbsbSMIMmgizc6UHdbsQ2hrkxZ7hAZARaobm/K0cm797IRtZGD+jqtUI31odBZlIBBvj4K4lWHXk+MJQBcACcmmLZ5LLNWX3I5tEQZynPKU3RLZnArxz6s/pvjv7wUud+P4vVWDVNsf6Zne7qCMbnpHj6PptnhxSNgTeeDvaII7ix49Xdq7dTAuyT7CGalRY1TvDTFCN96vQPTt6rz0DLkKirj6aRooDsF7KfwglmS46CU7z42ZMYAw==","page_age":null},{"type":"web_search_result","title":"The Latest AI News, Insights and Reports | AI Magazine","url":"https://aimagazine.com/","encrypted_content":"EtILCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDECEi8LsNN6PW9fEHxoMsV2T0WSSUx/mtzrkIjAHwWBtlzHd+CJJyEickg0h4qlkGvso+v+9v/3NC8DxdRy7PLYo1JYNmgdVfuxxVLAq1Qpz7B+BDU8+hhbPFnU+rHxaImCVmPDk2wd6odnljBRLTgg2EJjcLK07NXPN78+Nh2IzbgbYTjkPhzh2OxG6mrmIdfwcwuDSpnozNF0H2JnncqT/orVS66KSwbuXwkvMvgwJVe18du26EtVqP3/qhJV6nRAnc24M+ysPsxMXs6GmcpBblbzdWMBqWelABpGF4IrId6XvKzBdKvmazysCy+huSIg4/JlXbsTiB7988ZJ8ql05GoP+aHKYnM+5WhLYS3tcn4K4W2g6adPVeYbNUD1hL3zfd3APkrkP4yKRjY/Uw5VlS/zE6CEHMMV5BkkyllEOmorB2e6tT9JxyytMbDzohJxAVmDGxAcsTObZm2xrOWHlNsu0QMRdiQ30974unG5idQNyisS8PDdsc2SNg0YL2vcj93lkpVgsQGS0jeIfpHiKHaKR01d3Wv6+QSqRg6ABSbV8ToThoLPnNdq/XHe67R1TEGii8MqkW6Yw3I4EW7nAKjv7kbIYbSMaLgX4ibl6uriS0f/wdZ9kEf9iGIgXbq7Kri18h+QwFf/A7sX/Hg3hTmczUJQf6TNhFN0gTU8yTSqsuhATw5WirUJ2M7etnW+wk1oVYPbICnVm6SQZYu9jw4qihP7zOpnBK1bAECqrd0Nuoxy7jP8W2GEowlxenHR4h/5kwck+r3Jek6ai96QaMBWBBEy3ltO6THLlIdS0Au4aIF1u2VnF4DKQwIQI3e+May+9NPfV1Eaf8aj+xplp7ac41FGZ/2xFQqL/E78pqjqllpCkU6tJnRPEg8oWQ+noQN84zhE3byG4v5O0RjU1GRvSrVglSWZtHF/90ap/qlcSBTjM0+zbz6X6mCYP0+PQjGLFjn2N2mw8070WdxuOohy6S6wx6mISU3ob/hw9S7Z2ebG8kEKlGKAoiWD6dXEDoAwL9ViFE1fE19RojQwqwqBd3f3Sk4u8c7+z0KgwnmGGCvdG1xrIXtZGsSHFY7VFL9y6aBFyfCSJUnuQroxg5RML1SAHFVXoB3Zu9x4KDyfVTVUZmQNRh3flbPD5dutc6jiD2pKEFEy49kk3mX1CsuP2/3xqN8/TK6ZJM5OlnmrIPxl9+LJGZXVsAR4vWtfc9gs6HYLYrgisaxPPSUGUauL8cFKj6Fz1cEqbZ9l1mr1XhDmlJYU1qQG5suUh/qODrasrET2WBgfIMVlux+CvODH9nSlKDyrjUX+1YGy/XZ9Z6gN+q5O/cwAMbHQNRoWkZmaLjOPz5IXyLtRY7vrdh0XldNzk6YoQuyNvfVYq7uX0k/SejCEB82lNsuapiNvIEOo0HLuMr0c9sy9mHpHt5x+YM5UByivqWBjoCDVGaNlcRf8S21x6LkVOKsIb8hptmR1AUqMuUj/mVSkGWtAmSmlR9AtQtcBYTskrUrscStGIWYl7jQy9fcr+lGNkX0LLl6XuZ3vJeKRRKXCTGXKTRDiHOzB+wwcf6yPWeS1E0wDq9PnJrhWZUKwJwfs2vbr3bFWSC0p4CSiSdkHfHv++BJfzY/QHq6UaO0B+XgypCgnaRQe7e+aZBpkvIgJ/MYv1R0yXh79eOwdWabEQXNAIp/CuRYiglbw9Ci6eHdk3blqlg0sA/xv5I7yLNH4rwU2ICMcfqQ1qgEq2NQmTHOwkoiuc5EoLGNfzO6F/PUDNn99WHRaTWq6knzU0DK2xZRF0bL0qMQHQsFnGKDTJ+6l/BWyxqgMLDyb10VMxM5uW/cNoSAdZ3g8k4bvM8qXrAsbwb664DrykDxK0KN6Mm/8Db/0few5JX1SVGiprvsR+Rf6FEKG3X3D+lraYxijzQpJalMYYAw==","page_age":"5 days ago"}],"caller":{"type":"direct"}},{"citations":[{"type":"web_search_result_location","cited_text":"● LIVE Updated 0m ago · Edited by Alexis · Daily editions · About the index · Bloomberg and TechCrunch reported Aug 16 that Stripe has finalized a dea...","url":"https://aiweekly.co/ai-news-today","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","encrypted_index":"Eo8BCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDLYdXexr1YwVnMFUZhoMHoBkNL887i6iqOWaIjAaJ2IRsJ0LhoFyWxqf+djRzmOexL+MKS/o+7Bh7Tb897Q3mpgjV6gKA/CmSweEXj8qEwIWxv1D6SgFDD1ImXtAvUBjn0QYBA=="}],"type":"text","text":"Stripe has finalized a deal to acquire OpenRouter for more than $7 billion, representing a more than 5x markup from the AI gateway's $1.3 billion Series B valuation in May 2026."}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":8685,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":122,"service_tier":"standard","inference_geo":"not_available","server_tool_use":{"web_search_requests":1,"web_fetch_requests":0}}} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-d7272d43310d.json b/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-d7272d43310d.json new file mode 100644 index 00000000..7637e51d --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/anthropic/__files/v1_messages-d7272d43310d.json @@ -0,0 +1 @@ +{"model":"claude-sonnet-4-5-20250929","id":"msg_011Ce9f6KdvARZ8uGWz5Q5x9","type":"message","role":"assistant","content":[{"type":"server_tool_use","id":"srvtoolu_01TWkYbRXNbwHNVG2mZcUjsu","name":"web_search","input":{"query":"AI news today"}},{"type":"web_search_tool_result","tool_use_id":"srvtoolu_01TWkYbRXNbwHNVG2mZcUjsu","content":[{"type":"web_search_result","title":"AI News & Artificial Intelligence | TechCrunch","url":"https://techcrunch.com/category/artificial-intelligence/","encrypted_content":"EsIFCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDLO3tVUHtYQHUDS98RoMzdFIiTgG1F+6JbQeIjBK7Pd0UYOeat0H7kkdbwn6y47VvGZxVh0DiukM7+CnAKou4HBi3J1xb9uDOqm6BDkqxQTguet9teUgchPwYiVVEhxwCUubzjEDWFYErJs22cnkXBpc4tTQK8DPCFSz4YM5kr5L/eoudS6G3/ZR5r5owPSpsCCUn+LO0vN3e1BQSObxzDRRs0FwtEf1t4+0ETCvYP0R51x5GeD/MrHbPzkhHCg8a9V/excvmHhTTfSSqhkYOWea0dSkcx9uMwzXk9/M+5PC+UArJtu19lGyBfbBjRny/YYFXewC/Jlylh8bOSyW8lgJRQ1WNesFr6mzw4DAnQQ+kWfTgGgY4p3mQJi+AblmO9RLCoiNemMWaRPQqFCgb6TL4cYBkCmm+0FJ/kNp0u+EzwC33+5Aiczpi//AT5Tq2QIKGwhKwPjDRrmNu7H+ftE+HdA1dTJdI+N6KWxhRn7QuLyepj4ZK7ANmTG4yXBTdySJedZaZL9BpXJQsLGIHAb+zGdtSFOkj+wFBjiAC4gakcE2T8rT8mNjA7/lQxY2xqTLPKwLgjSuTPKJs8xybyCaZxGsAf7rSoD96xfaQSKxJjUOV7XL3+jM0Lmb6umEWVsCojxmCVZpQOPJNEJNTmTsjIMWUJPWyRItW1bsriej2qccA3imL12yo/7xxN3g3brpqeRfXBL0987I3T5Ci6zGcCqVLDEbJWA+xWV4dszuIFoY/pefLOt2OyvC44YImokQoC0TWqQvdeMR9k1l9KMl/fgLWUajpP3qabbCPke47qw7MrRMvBemZaOv4YCHkTo+oQ1yuqZC9+eEsGC5aU7nSuYH2C5EYOSqtsjlBpSFJVYldBgD","page_age":"4 days ago"},{"type":"web_search_result","title":"AI News | Latest News | Insights Powering AI-Driven Business Growth","url":"https://www.artificialintelligence-news.com/","encrypted_content":"Et8JCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDBFTNozhw3759jvvABoMsK2fyc2OG1mdQtHxIjBoh98cjI3bOZaZsr9sPRsYdKpZdQn358GfoKGmMixTj3NaIrkGshmt3gDhJEgVHnsq4gj3EewLKHNaIDhOT4ecntRwLcjbXydTsmyv5GyZJhuGXMIRse+oym3vX/LVbLJZooYga/4X6In10ZsCfzc/CYPiI0yfZCZjDaa4ANA4s6okyAERZfQMJdQUNAW3cdoy6JB8XHwS0BUcueIhR6HYfNVGOYub1M6Ydy/p4f7R7K242zRIhga1oU3oDU97Hfi1aE0p8lNZfHeGLo6nE+uVYpL0quyfva96SvO6MKBoFSaJ3COq/0dUl9yGDyH1Sn3E9kJMoicOzsr5vI+uT+AGhoSqxuWr1/E5yUmsuyDNt4qdfK/TwUDu5tiLdvvGqTgZ+yy4S8FJwBt0Sb8aKDr48KeGuKLHFA2OBl7pKEmPKjNUxNWoOUzccUuoxXN2ZVuD8JcNjM1sMf+cJZbaQVwQcHyLfwrEmnrSaeuRji94DrYcgK6pSnVAixK0K5zJQT+dwJQrwhPIRRtt2qbmQc8kx376usezjk53KFKzSPzj2QePl6p/kOEEtsIn+UbqOED8G2OB4qmE3/GV1436J0Mw+FsdEAdEkOMHagnrAdzbtsC7dZ6OQS7wf01YjcOv581ZQZgNDm+2iWcXcqxPqvXMhzKoS1g86lrf3dNTSH0fWONRbxZHPu3VF4d8x28q0HmjOViSpmUS59xCM+fAG7KFXZDxKBMyZvU3UAyoKypfsqD+E/3RFnkI2Yvn6IknH3IB2mIrkypaC3JgvwJSbPzQ6liq9dBsuEHlVhh49r4/HKOjLtb5gF+UT0QRNX2136g+oeDDANX+Y9nM2elQbLl8jEa7XzOqXpVNnqmCkZ+XTWJ+GV+jClkrOWrgtjtKgiKW01hm1NsVyuKQ7K7yD/J4gsR+vI5aE/6WVx9ltjAOEp5RiiX/C9z9m8VIpSymqSGvzGw8KnwLPq1JIx66v+tMQMCR4Ykg10WPs0QKBej53q8D6ksiV1H5K70oBNLUPWawCr80S8oQVRE8faINljuYIo60NZegPvGmnF/Sm16X9mVYnRmRFtuQZ6olLa0MfRzeE1tUQRu9/sJ9fxE19f5pbnxALsJO5GXP0sEUU0FmRviqK/bl7eEuQbOQ7K4OGYWdbBcTCCHQzAD9ALBhMNKPbINbYaW7CniNpN3XP3ZffWwSLpeFb2Bs8U0AUip6IogAtN+tOIPKHvOUer1IEz3RTzmsleLxBvjnIg9QMT0+w5SefqVfrajj2wKVPYIQePpZvaRbKDdfH6p7aFrCee1ZQdGKY5llJv+JmCojFV1UkrF2WnKsXBHKM1MhsdAW8X4D/TYqnMuh05ESFeIQoabHNWwgAsCX5WTu5nNcStZy8IHlR+YLdOZJKu+OzT+MQblBFgQREY8PkaYccPFMDLW3lso/Yc5RiZIKTxAEU1LSVrsC7Vb1qviOPOn1Aib5jVUbiurUPRO2xxTpES64i2BR4WgnVjDk/ruyjaRCapgpj0aiKB7opzMH4ClPaGyKyaZJZ31AhXwmSiACvqjWOyZBCspevEAYAw==","page_age":"13 hours ago"},{"type":"web_search_result","title":"AI News — Today's Top Stories, Tracked & Ranked | AI Weekly","url":"https://aiweekly.co/","encrypted_content":"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","page_age":"12 hours ago"},{"type":"web_search_result","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","url":"https://aiweekly.co/ai-news-today","encrypted_content":"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","page_age":"1 day ago"},{"type":"web_search_result","title":"Artificial Intelligence News -- ScienceDaily","url":"https://www.sciencedaily.com/news/computers_math/artificial_intelligence/","encrypted_content":"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","page_age":"15 hours ago"},{"type":"web_search_result","title":"Palantir Leads AI Data Deal With USA Today Sparking A Newsroom Revolt","url":"https://www.forbes.com/sites/sandycarter/2026/08/15/palantir-leads-ai-data-deal-with-usa-today-sparking-a-newsroom-revolt/","encrypted_content":"EuMECioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDOWHLd85yPRfkhMRRxoMXYWDMl+g5Watqgk5IjD+Nw4b8RpIHCQLqDnX5Fsmop3ZJf8b7Esn53yNQuGpluBa/jVAi22IhVYgtGNa6IAq5gNBC4ilVMc4vtYfLb+lDC9CW6K60NcCszV3gbmroWkqDJdIfBUUbBkWIW4wYCvpn2j3ZWTQ6Jt6ZQyD+gPOTtinuTSk2KPF7CpVjsDD9fJ2HLhCGb7nz5NJwCpunRy5ku9jdofni4UU/G4cTpFlJGNaruTrQHY5MO6ikOYsLDKDH2ZdswQ2eV47L/VcsY7pmzCFPhhSkyLedfidZjxXrl0gNrAydbmZXD3ZbkV4g7uaKlkfykDqrOi1HkVSIZqb5gMGPFm0SX8Ahjwy1ulWpE+6hzGZt7u/OHngfIgIfw+lvNDpuyXTyaJ4SfQFRVIlXGe4it5d5KY4es2pQ4baCM1be+Xqsqux/f87XBOiXcMumsmUL6So6Madenm/DIDEUqc0N2ftp+9AzOsnEj/S4/2YBJC6yvo6Wui8iSwthqkRsrokN0yQ3YLcNDWqc/uOiT6yNZP/q6WHS/i1YcE81q0O3cGDu1X0hyCMZwF4KRKgZqYAE8UoygqcTrpxadEVLZs7JfaPfySMR+60GozYPyXvca7BvTa5ZGlXOTJj9Xj/nswtVAT0ObGSgzVVP6RZHlkCkSScfVIBrOW3yUeidy2g7HYYBkWXNTy54YvbfFeQn/WPgiw0zQz9n53z5QQtzLcz0aQCJeEYAw==","page_age":null},{"type":"web_search_result","title":"The Latest AI News, Insights and Reports | AI Magazine","url":"https://aimagazine.com/","encrypted_content":"EtILCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDIzTdnNeFdJaaAZ4dRoMI96rNCNOurFza2vDIjA/A0QpEV80U6ugsKDjnGlUcmY31gu21Rwi95JCZaPCkMegrlA6Zytf3QAlSa3dxeIq1Qr/KNnsXToQaFBt9z9lb/3GCyO3vwXiribEzFiyUsyITo+APJRsgjMNZaO01HDCl1Nbg0ww5kxUp4V9NsfaUCAl7RGHvyOjiCM73KYxot4BFER51jpxrzYdVJ7TL6y5TmquAXbtv4hswHrVoD6NhStr57/QljGMVkdTXqwsltwZ2AOkmqnW24I063JhYsxrFpnv2J5is/CBnGJWIT0HopS9VwxqZFFVrIzqcRmcCX5cU91AwStI1ycfyfnyu8TCGd4rOwa0dWOxyxIFz1tuNzUtjX3wrJaQWf1umD1DCO4PyusFM3UnW1aimeaWYJGugubl+MjNZuFuce/vl4lXe/uqxRMrM6BRCcMDoJVLtBmfK/2ig4RrOBIzVMwfzaNSmQhGplH7BrLfAtq0YnzOLNk/0wOFR0O5pwblhL8L+KzHpcVT66+FODYNQVctv56s064asc4ylirbGXg0zBKMpUQkMKfejgwVwDlMjA/4cssMkplHg3Gy0+PlqetMRbWoQhQkKckhlUXuQbmr5yODl+8FVVnf01lFLYHBbGl8aavc3rTFU0Dompbcrk87Fnwn3uHr+hlnX0hCV0XVoBM2pxgp3SHHHkUmYdngfD1ug/oVaVsiBfOKEHTQqDavHXTQOSoc35K4DQHPbGnvpW+4ueMRyRwtk46519vgpeCuhgWBoLNSi6Ppf0KXou+NxxHyHmbvvLDR7NlV7JsULnahKHjX3U4lXcx4xKAw8Ker/T0ZExyu0uv+fBrfQvMtuXPFFyV2vKS5RKjg7GwZhnWWKjgBofvhI4GmaMLBomhIXVbrA907U4MXQ1k2WYUZOMB61m5mgk2ucENBHjKe2rMFSVcRhzkYKEwF+F5FqyMpNMulmxqEMdIX3WOUCcnn3Z7xAYvn797OUjYR/GIzHK9NF9IqaV8HN3/e0FaOXQ3l/xK3BFaajnXhQTAaaPKAg1jUo+9yItYNfARvLeRIgIZcfcWRKczfdNk03BaLwkW00Cgutd8I9gsnwSfB5hIFckTCqVZZoptGyviEJ/y83Q+kXEGFEtTnQsYPR+TtHu8homPMldmz2XbOs9+YLvY9TNOuVS3T/HGTgzhBSn4VOeQ4JSzgFNioALzkwxbuwqsIVGnw4si1UhYAy7Lhyg2HUrfdlYG7+ENDem83Pf34USCLD9vfP/v0p8qaX2Yg0GklB8eUIuETdt8jh60sqdNxdvuTood7W+sDwoXCDZ7zVN0xbHmUFQQHFQmn4S+HbMZa/oXF7guL4+7Z0fUNMehuZhLgMqDaa9LQeLB1Q59rqQb9UGULtkuDOhusInxT+rIkPkfmnDjaV3qTDt6Luq0fyyHoM08jbCEwchH+H76l6EBcSEhiU+NL1N1J2aoFUGaP1RP5kxAK/dcHu/vcHdRaLADDHuy5Kaob7W057zGt6KCOjOt1cD/K+zTDv+hbjg+AU/6RjGagES4NQj13CxWfkSKv8WQEkP3y7EvMRI/om7ViZF6cNQw70LpWhxZqMtIVRuiC6nH+aqa/PTR+9IZPTd8GPcKJ53EdJf4saKM4uTaE5EwUvl5q2/GIk06QBhDmMTZS6eH01QXnk2xWxxXPwQ7eZ39iLegFsjEybJKkboj232iU0OPzOLTj0GS++P5UzCP/XLtt6YzYthkv8Ch9aUx/ZrJ7m70llOCV3JhvKFAfzOOuQO8Pbkub/sZcg+Cc8j3WGuy5my3RyijgB1LtxZqzk8ED4zXItaTt6Ug494FXaU8nLGcsooxEpceH1Roj74UjVn69T2l0YbgsF7vKdhZV23tfrf7ltasp/VrhbzlN98FS6n0bWWUYAw==","page_age":"5 days ago"}],"caller":{"type":"direct"}},{"citations":[{"type":"web_search_result_location","cited_text":"● LIVE Updated 0m ago · Edited by Alexis · Daily editions · About the index · Bloomberg and TechCrunch reported Aug 16 that Stripe has finalized a dea...","url":"https://aiweekly.co/ai-news-today","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","encrypted_index":"Eo8BCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDPEs9y20hx9q3le7ZhoMdFuMAg+RyL47TrC6IjDmN7pDkMA/2+kcC7xUqI2GmuqEA7iQQsZjrOGjQ+Gg7UwxU+w4yv33QqXaZSwgkOIqE2Cw/SNFUFccalnm/QLu803lTnQYBA=="}],"type":"text","text":"Stripe has finalized a deal to acquire OpenRouter for more than $7 billion, representing a more than 5x markup from the AI gateway's $1.3 billion Series B valuation in May 2026."}],"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":8656,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":122,"service_tier":"standard","inference_geo":"not_available","server_tool_use":{"web_search_requests":1,"web_fetch_requests":0}}} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-10af9cffed94.json b/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-10af9cffed94.json new file mode 100644 index 00000000..6b70a01a --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-10af9cffed94.json @@ -0,0 +1,53 @@ +{ + "id" : "78c9b5e6-7f0b-3c0e-ae4c-c0bf1dd6f61e", + "name" : "v1_messages", + "request" : { + "url" : "/v1/messages", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"max_tokens\":1024,\"messages\":[{\"content\":\"Search the web for one recent AI news headline and answer in one sentence.\",\"role\":\"user\"}],\"model\":\"claude-sonnet-4-5-20250929\",\"tools\":[{\"name\":\"web_search\",\"type\":\"web_search_20250305\",\"max_uses\":3}],\"stream\":true}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "v1_messages-10af9cffed94.txt", + "headers" : { + "anthropic-organization-id" : "27796668-7351-40ac-acc4-024aee8995a5", + "Server" : "cloudflare", + "vary" : "Accept-Encoding", + "anthropic-ratelimit-output-tokens-limit" : "2000000", + "anthropic-ratelimit-output-tokens-reset" : "2026-08-18T05:04:16Z", + "anthropic-ratelimit-input-tokens-reset" : "2026-08-18T05:04:16Z", + "anthropic-ratelimit-tokens-remaining" : "11999000", + "anthropic-ratelimit-requests-limit" : "20000", + "anthropic-workspace-id" : "wrkspc_01J8hau4yJ2SKvtbeP9drm9A", + "Content-Security-Policy" : "default-src 'none'; frame-ancestors 'none'", + "anthropic-ratelimit-input-tokens-remaining" : "9999000", + "anthropic-ratelimit-requests-remaining" : "19999", + "Content-Type" : "text/event-stream; charset=utf-8", + "CF-RAY" : "a2ce607579fe75e0-SEA", + "anthropic-ratelimit-tokens-limit" : "12000000", + "cf-cache-status" : "DYNAMIC", + "request-id" : "req_011Ce9dVavqUS3NcYEqM3JkS", + "anthropic-ratelimit-tokens-reset" : "2026-08-18T05:04:16Z", + "strict-transport-security" : "max-age=31536000; includeSubDomains; preload", + "Date" : "Tue, 18 Aug 2026 05:04:17 GMT", + "X-Robots-Tag" : "none", + "anthropic-ratelimit-requests-reset" : "2026-08-18T05:04:16Z", + "Cache-Control" : "no-cache", + "anthropic-ratelimit-input-tokens-limit" : "10000000", + "traceresponse" : "00-e655b7e5083fed31f3ad8fe990b07810-425052d11c29900d-01", + "anthropic-ratelimit-output-tokens-remaining" : "2000000" + } + }, + "uuid" : "78c9b5e6-7f0b-3c0e-ae4c-c0bf1dd6f61e", + "persistent" : true, + "insertionIndex" : 25 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-c0794efcd5dc.json b/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-c0794efcd5dc.json new file mode 100644 index 00000000..6bf9a84b --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-c0794efcd5dc.json @@ -0,0 +1,52 @@ +{ + "id" : "454a6457-ae7a-3b31-975b-bc607fe03633", + "name" : "v1_messages", + "request" : { + "url" : "/v1/messages", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"max_tokens\":1024,\"messages\":[{\"content\":\"Search the web for one recent AI news headline and answer in one sentence.\",\"role\":\"user\"}],\"model\":\"claude-sonnet-4-5-20250929\",\"tools\":[{\"name\":\"web_search\",\"type\":\"web_search_20250305\",\"max_uses\":3}]}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "v1_messages-c0794efcd5dc.json", + "headers" : { + "anthropic-organization-id" : "27796668-7351-40ac-acc4-024aee8995a5", + "Server" : "cloudflare", + "vary" : "Accept-Encoding", + "anthropic-ratelimit-output-tokens-limit" : "2000000", + "anthropic-ratelimit-output-tokens-reset" : "2026-08-18T05:04:15Z", + "anthropic-ratelimit-input-tokens-reset" : "2026-08-18T05:04:15Z", + "anthropic-ratelimit-tokens-remaining" : "11994000", + "anthropic-ratelimit-requests-limit" : "20000", + "anthropic-workspace-id" : "wrkspc_01J8hau4yJ2SKvtbeP9drm9A", + "Content-Security-Policy" : "default-src 'none'; frame-ancestors 'none'", + "anthropic-ratelimit-input-tokens-remaining" : "9994000", + "anthropic-ratelimit-requests-remaining" : "19999", + "Content-Type" : "application/json", + "CF-RAY" : "a2ce60525f5eec58-SEA", + "anthropic-ratelimit-tokens-limit" : "12000000", + "cf-cache-status" : "DYNAMIC", + "request-id" : "req_011Ce9dVAxgjvKeRRne7Sucm", + "anthropic-ratelimit-tokens-reset" : "2026-08-18T05:04:15Z", + "strict-transport-security" : "max-age=31536000; includeSubDomains; preload", + "Date" : "Tue, 18 Aug 2026 05:04:15 GMT", + "X-Robots-Tag" : "none", + "anthropic-ratelimit-requests-reset" : "2026-08-18T05:04:10Z", + "anthropic-ratelimit-input-tokens-limit" : "10000000", + "traceresponse" : "00-bba893816bcf76d673bfa5806c94981e-d0c3287ada920b80-01", + "anthropic-ratelimit-output-tokens-remaining" : "2000000" + } + }, + "uuid" : "454a6457-ae7a-3b31-975b-bc607fe03633", + "persistent" : true, + "insertionIndex" : 26 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-d7272d43310d.json b/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-d7272d43310d.json new file mode 100644 index 00000000..c864823c --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/anthropic/mappings/v1_messages-d7272d43310d.json @@ -0,0 +1,52 @@ +{ + "id" : "8474ba87-903a-3635-9126-e84b6c20e0b9", + "name" : "v1_messages", + "request" : { + "url" : "/v1/messages", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"max_tokens\":1024,\"messages\":[{\"content\":\"Search the web for a recent AI news headline and answer in one sentence.\",\"role\":\"user\"}],\"model\":\"claude-sonnet-4-5-20250929\",\"tools\":[{\"name\":\"web_search\",\"type\":\"web_search_20250305\"}]}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "v1_messages-d7272d43310d.json", + "headers" : { + "anthropic-organization-id" : "27796668-7351-40ac-acc4-024aee8995a5", + "Server" : "cloudflare", + "vary" : "Accept-Encoding", + "anthropic-ratelimit-output-tokens-limit" : "2000000", + "anthropic-ratelimit-output-tokens-reset" : "2026-08-18T05:25:19Z", + "anthropic-ratelimit-input-tokens-reset" : "2026-08-18T05:25:18Z", + "anthropic-ratelimit-tokens-remaining" : "11994000", + "anthropic-ratelimit-requests-limit" : "20000", + "anthropic-workspace-id" : "wrkspc_01J8hau4yJ2SKvtbeP9drm9A", + "Content-Security-Policy" : "default-src 'none'; frame-ancestors 'none'", + "anthropic-ratelimit-input-tokens-remaining" : "9994000", + "anthropic-ratelimit-requests-remaining" : "19999", + "Content-Type" : "application/json", + "CF-RAY" : "a2ce7f26b80bba52-SEA", + "anthropic-ratelimit-tokens-limit" : "12000000", + "cf-cache-status" : "DYNAMIC", + "request-id" : "req_011Ce9f6GB4PWSdDnpWhdxbT", + "anthropic-ratelimit-tokens-reset" : "2026-08-18T05:25:18Z", + "strict-transport-security" : "max-age=31536000; includeSubDomains; preload", + "Date" : "Tue, 18 Aug 2026 05:25:19 GMT", + "X-Robots-Tag" : "none", + "anthropic-ratelimit-requests-reset" : "2026-08-18T05:25:14Z", + "anthropic-ratelimit-input-tokens-limit" : "10000000", + "traceresponse" : "00-f711a485285ab38714b55ee1fe88aa68-b8ad80fc286ded83-01", + "anthropic-ratelimit-output-tokens-remaining" : "2000000" + } + }, + "uuid" : "8474ba87-903a-3635-9126-e84b6c20e0b9", + "persistent" : true, + "insertionIndex" : 28 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9744b37774d9.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9744b37774d9.json new file mode 100644 index 00000000..a1ef0894 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9744b37774d9.json @@ -0,0 +1 @@ +{"data":[{"_async_scoring_state":null,"_pagination_key":"p07675235569653186560","_xact_id":"1000197707053523103","audit_data":[{"_xact_id":"1000197707053523103","audit_data":{"action":"upsert"},"metadata":{},"source":"api"}],"classifications":null,"comments":null,"context":{"span_origin":{"instrumentation":{"name":"braintrust-java"},"name":"braintrust.sdk.java","version":"0.3.20-fd0c84a-DIRTY"}},"created":"2026-08-18T05:13:52.022Z","error":null,"expected":null,"facets":null,"id":"d98e4897dfcb00ea","input":null,"is_root":true,"log_id":"g","metadata":{"braintrust.parent":"project_name:java-unit-test","client":"anthropic"},"metrics":{"end":1787030036.364329,"start":1787030032.02212},"org_id":"2732c14b-91d6-4629-901a-c06fcc0df441","origin":null,"output":null,"project_id":"946b2c8d-f5d4-4690-9491-d79e2857c995","root_span_id":"3dc06fd1dbdf5809ad38fa34d7c94014","scores":null,"span_attributes":{"name":"web_search","type":"task"},"span_id":"d98e4897dfcb00ea","span_parents":null,"tags":null},{"_async_scoring_state":null,"_pagination_key":"p07675235569653186562","_xact_id":"1000197707053523103","audit_data":[{"_xact_id":"1000197707053523103","audit_data":{"action":"upsert"},"metadata":{},"source":"api"}],"classifications":null,"comments":null,"context":{"span_origin":{"instrumentation":{"name":"braintrust-java"},"name":"braintrust.sdk.java","version":"0.3.20-fd0c84a-DIRTY"}},"created":"2026-08-18T05:13:52.242Z","error":null,"expected":null,"facets":null,"id":"71d8b6b93380a09c","input":[{"content":"Search the web for a recent AI news headline and answer in one sentence.","role":"user"}],"is_root":false,"log_id":"g","metadata":{"braintrust.parent":"project_name:java-unit-test","model":"claude-sonnet-4-5-20250929","provider":"anthropic","request_base_uri":"","request_method":"POST","request_path":"v1/messages"},"metrics":{"completion_tokens":105,"end":1787030036.3637276,"estimated_cost":0.027543,"prompt_cache_creation_1h_tokens":0,"prompt_cache_creation_5m_tokens":0,"prompt_cached_tokens":0,"prompt_tokens":8656,"server_tool_use_web_fetch_requests":0,"server_tool_use_web_search_requests":1,"start":1787030032.242451,"tokens":8761},"org_id":"2732c14b-91d6-4629-901a-c06fcc0df441","origin":null,"output":{"content":[{"id":"srvtoolu_015UtttFnSXF9QTw2RbVfLFj","input":{"query":"AI news today"},"name":"web_search","type":"server_tool_use"},{"caller":{"type":"direct"},"content":[{"encrypted_content":"EsIFCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDKr3UnB782FMbGi7mRoMvhOV+vSKnNp2ocy1IjA9nkMzNXCzsGeaHPXBUhyEAu2QP2C5fzLiFnP6Pcy1ySw2O0xNXR6cBaTsldu82BwqxQSmomxhlOZRKm1rDI6SNhdECxzLiq4S0a1CB3ETZT+FWdidU8c8pe+1FwerrvWK4X1TTRp54Hpqe3ocZdlptmJ0fkIjvfR/yS0bdCQs7gaKpr9k16j99Mpxfy1Nrq71qEJ7CSJRXMDo60PNAVGH4gRnRHP25TYMGT3IUInhLrmSvKhDC2/DBpmyIHA+uv3demj+mpcW6imijthhsYi+9RO2XbZoQz92Gft6u1c5rVwnJ6NlrZ4yxcgbVIrkjhfEjknNuCZC3RzybqFlUQJcXdC8FFv3hCaCUeXQoh3GNp44uHJ8VFnE/3FpXfNmGQofr8hlHYm55SIQvORy8Cb3dpZigh9qKbnDZ80sE/22+3MptB1c9MfhroltTHp1jH0ms8wgYRwZ5F/RqP3uaelnnAB8hazC6d51SRa8ADkK29lYwryQdrNUxeJLAaw8zE8Ci7YQEG554ZaHz74FH3YrUzvhH31w9NlHFM2T4pexZ9oZMsXW9sY+pejzJ2LqpF3d1GTKOvikq/XZhQdSrwsxSYGp2pRvy1LEhzPx4SA8UWAhrZ4evPhtjSjI7gKqre6vt8/mW66qD3iU4TBHISlFcr+gyFiFOvIPtP6PUxWsImvOb+uOzDYOG0Do/itKh72S7PdVybw1JhjMh5ePtfs2z0s16qwNSXi7lGFUoe+t7tztApjVkkhazUrU4+OCmEIJNWzhqsAAtrs5Ve+wlXzxnApG6Mk1GyO7dhIuVHw/mbTKJp+j6lkBYlbwUwCbPI8jg4mhhEqrmRgD","page_age":"4 days ago","title":"AI News & Artificial Intelligence | TechCrunch","type":"web_search_result","url":"https://techcrunch.com/category/artificial-intelligence/"},{"encrypted_content":"Et8JCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDA2O28LNqLeWxVbpGRoMGelDBUSV43wOqd8nIjC5NwGRdqb3tbfG6p+WKUf8FP7+d3xGLBW9OaQeDY3U+AsVRRrNGR3AnfK1RsgF+noq4gjGvy5GMp5Yf+dtB0IRctv7Gip8xLeDHgG/PDwVxDUwqZ9aR7Xt+MFcUs9eWJbuZR8/spLm6o3JKYu1u4uI38kzRmN/xWV7DbOiR1wuyxB/jQ9fzh37VH9tOdeMbWLLF2yR2jvjmxCqTK11oAscCZwwTSL5wS9Ss2qwvTKIiftTrhPaz/lIePkfjp7PrbGjVQDCcoTvYtgezm4yBeVih5gv41RW7Oitzv4BX/GcKg3FGEO9EjJ9pJwmHfRqaoTYrjl22s2Hs2OefmpILVl/Gs6qgsJodtFVvXeruKIk/A9BXZDIAOL8D6wDSHLonOFstL/jpyqBUC4m6MrPUFbfRSR21kXLvKf1paQXHaJ8CvP8r+NjcB5vmOqXc1r3o9SawEwYZQfckumI/FgERlrUv8AUBISfNUFy6q2YL7N4krwMRMnp6DqSqFIwyMgZxwx+og7dTXiyZAgsrtAvkJkeycWxA6QIPeNbqymAm4Y9yFHRsgDGe3OasA5VH/5uDVxFxSYWJPLX2xp2w21mlTBoa7x7/wP6brnaAD5mVZckHCK5mX57sGybblFR4fbg/tuLmMg7LZIhp5ZANQfHI6iL1qabUVB6gY2QvGmGjmQuArwC2rIVS5rM+sNqgJ3fE3XIBcSky7FvVu0uL7uN54ds9hUKqu7FDiNVqbOJYGjoKoGXmD7QEtNHxguaGu27ftUoYxP3dhnhrQoVnZXMWe62/FYP5W22tkvjoDIUcrAtpOtinsTVSB08f7FvAQJSVCl4hBZcqyNpvcUNe6q2bOu5H+8Ibv/r6qWr7IT1TVIVSRqdyMkgWDjkULlthib46SCkJQjp7kX9p66A4N81QOI+RguTV0K0NV8XcI+sMp4wWXPAThxaDPYtDKDj06Ogv4A8ysvF9GOLlWtRCzJYITYrJzRugL+TdzVPRVKvaiN7DZb2z36ivgKPESHX88FalK5/OiZ6cb29Lv4eMJ+vS3HpTE/Lv4gr2W0u1sX2AM2LfeHIAsEduRAxk4v/6jWbG18M/ePQd7zjXuNpfgcNWnzD+gWnT8x29gz8PolX2xZvh4d3S4cCoXnYe4hzyIRCIrjrHFU8OX9PZ9VURgYgA11HPUbhPVKMxohjByjcYE+1eGdskXXqcsmWR62NdiUpsf9mFqtiRvu6nK2DMYCZrgbwYnhYFnDr58Vh4O9pHfIdnvkXSVHdwPPqVJUffx7WkM1mF613P3tukC/t2iUpi3hU52wOo/FcSB4HIf/Q+8T96rzI0y3GbfxcU8h+GT/Y/C+wowkLjV2WDETmC3RCWTB3z067J2wNbybKsRcpgtjjqeFvEmdkCsyqDMzOEnbLTyryXlc8FtujdLknDH6cZeA+Cja5yFwYGdSbtYzCvDHvG5WJHF4/n+anUc7td7qjTlHi+yHEo46FmPEcEwrFG+QbNlTj+GL9tSOOitN6OBLl3fdggoN3PQzrpmKs45K938U4tVjCN53s1cnKGgh6Fua9QVuYR9oYAw==","page_age":"13 hours ago","title":"AI News | Latest News | Insights Powering AI-Driven Business Growth","type":"web_search_result","url":"https://www.artificialintelligence-news.com/"},{"encrypted_content":"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","page_age":"12 hours ago","title":"AI News — Today's Top Stories, Tracked & Ranked | AI Weekly","type":"web_search_result","url":"https://aiweekly.co/"},{"encrypted_content":"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","page_age":"1 day ago","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","type":"web_search_result","url":"https://aiweekly.co/ai-news-today"},{"encrypted_content":"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","page_age":"15 hours ago","title":"Artificial Intelligence News -- ScienceDaily","type":"web_search_result","url":"https://www.sciencedaily.com/news/computers_math/artificial_intelligence/"},{"encrypted_content":"EuMECioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDFGD2w+uxBU2EJM0rxoM2FTDL33gzjx74vzMIjAZLBS47T5SlAr+UPP8OaRWWSJH7+7RQkKy/Kro0uCRivQP+C2F3O4CGIqkJ5cu/hkq5gP5uqMQdR3Uc+Dw6xFV4V6NFD1vdhaakJfi8Cvk+qbm+Ijzi0NO4lowvdX19RCVGHiPqOI+D9xyAa05yX+cYLQYCSUYEJa21WAwtYFrn32+PELLZPrksbUJNnMWZdtuqARNZLhrmmu9GJbrr873Wnq/x5NrbZ85oZACuwYlGKPaY2TiiUsSn6HbxoOQhzs4PZ/rnvp2FvufE4Cl8GxQZc03FHkVMXPzhVc+94U5gmnssNCBj+h+aXi5aRqN8Dx5oKcvLtfSRt93+600qmyK48MyU+CT8+imMMrBZbwuo+Q/52KofqMfb0HRtVKtckrERybLG57caZrfv/9qZFcbNiUd9w2J31utGs+qZuzWiDs5459aqUy5sxpPmZyf3gRnSt9R4FJJ9rHU3282PN7MfeaKhjSFHkxZXXFP8n2cy42YVmf3FHsQSG516UElObDvzL+E0fwqkMPVCDMmRTHtv4/BprY9uDjw2QPLsKdGYvLP2TOGOm7VE9DEjzBramCB5fn5qTYcXbWNy8V1qaZJBL+4kTq5y2HtCqhLvA3taQ497rV7AyfNyytewwtc1nVlXUG94mU52/6wMj3Si3aTopJzVrWjTx8k3gWpX6wXDpHRSfXEHBR3GPa53dBhCKOipJRp75CRyXUYAw==","page_age":null,"title":"Palantir Leads AI Data Deal With USA Today Sparking A Newsroom Revolt","type":"web_search_result","url":"https://www.forbes.com/sites/sandycarter/2026/08/15/palantir-leads-ai-data-deal-with-usa-today-sparking-a-newsroom-revolt/"},{"encrypted_content":"EtILCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDB7PQqWXziYzzmrAjBoMU6Ch16X9JHzmSN6sIjB9UbazOte9rbASaiOfmkctLPBWUssUVlEylhP63vlhBK8x0lA6YUkez14VKGUKEVsq1QoNxoYwA6Ta1BmcTERZtzWRLfQTPU+3vWXDYP+CIOKuM0enIWWLd27yMJPbQ7AQ3Nys9f+Jg5YXdZIMXX4Yr2hfHquaYM5Qh4WLF8qB48OLXw7IeIQo32TM0aO1pJutS3g4cVc48pAdzjIAMREx7Alef1jHTLocbz9t+dbSkBa9IhdNboxPqgxZ2tdUaA2HnCLcT8VgiPRfd+0nkMhTtWjAoEt9KB9fkstnTV0in65onodEtm/zx2QktLpzCwEcahDjtss46BXe559/HjIasvVDP2qXEzNhCJJ0L5b7tFKsxuiI+WOMBQZmly7p7v9VfkxF+xXu22yECq3uRfq/ZJRc5YcP0skdmz4VwzhqyvPR0a2KFEGtR6eMD3tTMq/qEZ0WHbD1oqn+4jwuT8nGGCkhygv1atLu72q97IosPI05xFEObA55vAKMo/ZYT4F2FoQHDf8XwaSBs+743KV+N2XtkgtYjZXOjE/W/FBIygUlN1Lvk06UKa+pOyylZ87/G+qc8/VVcHTsIps0UV7jlngd52TU571EFXZX1QRybJ2GDYIPlpwtq8ufpEEFxj9CPjdPDohcuYpPJkUNPDpDUOqG0KVKpPrncabsqqbvDff1wSa0avqvmDhRGOvJ2bGkdlEA/YgNGFWZA1rWhudBEDzYnuC778h5LjCT9BFKefinntD4ofHWM3HdFyQTeOu75seC9e4v5A6TkcKWvFvBfB3HH6YzuoUVtVc3XjjjG+CXb3t7rbODxjIjURbiVQ3A5JmKQ+XGcIq/prCitqQ1k70F1HHzsI+HHX4XWpjd6TtUDrSerUjuxFXxCfreaHAjpQ+ubzRmDuA/DMVXDK7+NsxGnqjr60ZrJuGdODHAbIAKbPaMxoLqONcMSJ721LZvCLDCgLTmsE0SwTBiMUlvEnPCm0BASreWpcrUtTixJpcKjmEWOCTwBmgPtyNqZpBR4kXd+krj8eUwdQedwiY/YZDt1io/UAqCibL8ki1Ft7Np8oA87cv0G6Ri/35zzDrogwMho39sWUAPe4CT2k32oa3T35iDx1mXiruo6YHh9luPtc0UZUNdpNzVk41m4be7p+KSIga4rb9uDVN61vQR2QKs6kEZoXgcLWAINc2htUyYr1SIKTeQJQpW7ruOCClsDrPhTvsMPaWqlvVFxGPAjjuCfz/0SznVp35N1gkVCLKsOX7T9qTgD9fZPYUM2nR2vedJkXKMUmsWvqoGLHoF5Wt1YpFuo5wySyh7KFKrSC5WsYlEkrI+QigL5VnKPd5zyBnDqOtSKMTf18CUUcQl6tssKuSt4mmM3zFQ7PYYCFLA2vvpgceIiRseGvi298TbNgvBrZpqCvW6H3ri5B8qcF26Lecde9tx2H90uYbR+miJW3my62Mn3rRviF9jOTfOo1qu2YpFTjwqMGEjVWi+vnO2qTMohpxGJ0rCb+AiyCNL/2X+KdaUrDXzEbikA7BOjN79UqH9o1VP3N0dKHqlUL3aU36ZS0xSkafpQ+XrguQ2b2RhBrtkpMcGiCP3/59HttPNB+uy2jPnx3M7lR90a3j5ikUOZOZS0+AUMkvAzrRKZOiIiLOJdWZoh6tEnYiaZtI3rf+Uy+tqKwBkeSvYcqG7/yZVYjjtTlhwDIrQDW1T2eOWO7aWfeaPzr3h6Tttzh3VBtJPxmDhlW+LfWg+DLVIG/zlU2jCycR6mRmfdU3JS5+FNNxzsGlQ8ZWlNEZkjDrsD2UaNF4BTU2IldOVH8Xw6t31d5T/ty3Zw23OGi7XC3KIm1cEISOGgtrh/jAyb2BaWMrdKoI98xo2z0KSFGN24P+P5ZEYAw==","page_age":"5 days ago","title":"The Latest AI News, Insights and Reports | AI Magazine","type":"web_search_result","url":"https://aimagazine.com/"}],"tool_use_id":"srvtoolu_015UtttFnSXF9QTw2RbVfLFj","type":"web_search_tool_result"},{"citations":[{"cited_text":"● LIVE Updated 0m ago · Edited by Alexis · Daily editions · About the index · Bloomberg and TechCrunch reported Aug 16 that Stripe has finalized a dea...","encrypted_index":"Eo8BCioIEhgCIiQyNzc5NjY2OC03MzUxLTQwYWMtYWNjNC0wMjRhZWU4OTk1YTUSDKANIzjrwAejyxx7SxoMKZDF4Hp41qQ/MLjLIjDAKf5Kij5nxh4dOqHUXNqCY8VVLjiNAnFnJB897ItWQmrzNpCZFGMmuwUBEFUPSZEqEymupdgjfChzS63sDW/rdk2l+GkYBA==","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","type":"web_search_result_location","url":"https://aiweekly.co/ai-news-today"}],"text":"Stripe has finalized a deal to acquire OpenRouter for more than $7 billion, according to reports from Bloomberg and TechCrunch.","type":"text"}],"id":"msg_011Ce9eE5R1d9YoebCfJRihs","model":"claude-sonnet-4-5-20250929","role":"assistant","stop_details":null,"stop_reason":"end_turn","stop_sequence":null,"type":"message","usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"inference_geo":"not_available","input_tokens":8656,"output_tokens":105,"server_tool_use":{"web_fetch_requests":0,"web_search_requests":1},"service_tier":"standard"}},"project_id":"946b2c8d-f5d4-4690-9491-d79e2857c995","root_span_id":"3dc06fd1dbdf5809ad38fa34d7c94014","scores":null,"span_attributes":{"name":"anthropic.messages.create","type":"llm"},"span_id":"71d8b6b93380a09c","span_parents":["d98e4897dfcb00ea"],"tags":null},{"_async_scoring_state":null,"_pagination_key":"p07675235569653186561","_xact_id":"1000197707053523103","audit_data":[{"_xact_id":"1000197707053523103","audit_data":{"action":"upsert"},"metadata":{},"source":"api"}],"classifications":null,"comments":null,"context":{"span_origin":{"instrumentation":{"name":"braintrust-java"},"name":"braintrust.sdk.java","version":"0.3.20-fd0c84a-DIRTY"}},"created":"2026-08-18T05:13:52.242Z","error":null,"expected":null,"facets":null,"id":"2a810527d7497d4b","input":{"query":"AI news today"},"is_root":false,"log_id":"g","metadata":{"braintrust.parent":"project_name:java-unit-test","caller":{"type":"direct"},"tool_call_type":"server_tool_use","tool_result_type":"web_search_tool_result","tool_use_id":"srvtoolu_015UtttFnSXF9QTw2RbVfLFj"},"metrics":{"end":1787030032.242451,"start":1787030032.242451},"org_id":"2732c14b-91d6-4629-901a-c06fcc0df441","origin":null,"output":[{"encrypted_content":"","page_age":"4 days ago","title":"AI News & Artificial Intelligence | TechCrunch","type":"web_search_result","url":"https://techcrunch.com/category/artificial-intelligence/"},{"encrypted_content":"","page_age":"13 hours ago","title":"AI News | Latest News | Insights Powering AI-Driven Business Growth","type":"web_search_result","url":"https://www.artificialintelligence-news.com/"},{"encrypted_content":"","page_age":"12 hours ago","title":"AI News — Today's Top Stories, Tracked & Ranked | AI Weekly","type":"web_search_result","url":"https://aiweekly.co/"},{"encrypted_content":"","page_age":"1 day ago","title":"AI News Today, August 17 — Top AI Stories & Live Updates | AI Weekly","type":"web_search_result","url":"https://aiweekly.co/ai-news-today"},{"encrypted_content":"","page_age":"15 hours ago","title":"Artificial Intelligence News -- ScienceDaily","type":"web_search_result","url":"https://www.sciencedaily.com/news/computers_math/artificial_intelligence/"},{"encrypted_content":"","page_age":null,"title":"Palantir Leads AI Data Deal With USA Today Sparking A Newsroom Revolt","type":"web_search_result","url":"https://www.forbes.com/sites/sandycarter/2026/08/15/palantir-leads-ai-data-deal-with-usa-today-sparking-a-newsroom-revolt/"},{"encrypted_content":"","page_age":"5 days ago","title":"The Latest AI News, Insights and Reports | AI Magazine","type":"web_search_result","url":"https://aimagazine.com/"}],"project_id":"946b2c8d-f5d4-4690-9491-d79e2857c995","root_span_id":"3dc06fd1dbdf5809ad38fa34d7c94014","scores":null,"span_attributes":{"name":"web_search","type":"tool"},"span_id":"2a810527d7497d4b","span_parents":["71d8b6b93380a09c"],"tags":null}],"schema":{"type":"array","items":{"type":"object","properties":{"_async_scoring_state":{},"_pagination_key":{"description":"A stable, time-ordered key that can be used to paginate over project logs events. This field is auto-generated by Braintrust and only exists in Brainstore.","type":["string","null"]},"_xact_id":{"description":"The transaction id of an event is unique to the network operation that processed the event insertion. Transaction ids are monotonically increasing over time and can be used to retrieve a versioned snapshot of the project logs (see the `version` parameter)","type":"string"},"audit_data":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"classifications":{"anyOf":[{"additionalProperties":{"items":{"additionalProperties":false,"properties":{"confidence":{"description":"Optional confidence score for the classification","type":["number","null"]},"id":{"description":"Stable classification identifier","type":"string"},"label":{"description":"Original label of the classification item, which is useful for search and indexing purposes","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"type":"object"},{"type":"null"}],"description":"Optional metadata associated with the classification"},"source":{"anyOf":[{"anyOf":[{"additionalProperties":false,"properties":{"id":{"type":"string"},"type":{"const":"function","type":"string"},"version":{"description":"The version of the function","type":"string"}},"required":["type","id"],"type":"object"},{"additionalProperties":false,"properties":{"function_type":{"default":"scorer","description":"The type of global function. Defaults to 'scorer'.","enum":["llm","scorer","task","tool","custom_view","preprocessor","facet","classifier","tag","parameters","sandbox"],"type":"string"},"name":{"type":"string"},"type":{"const":"global","type":"string"}},"required":["type","name"],"type":"object"}]},{"type":"null"}],"description":"Optional function identifier that produced the classification"}},"required":["id"],"type":"object"},"type":"array"},"properties":{},"type":"object"},{"type":"null"}]},"comments":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"context":{"anyOf":[{"additionalProperties":{},"properties":{"caller_filename":{"description":"Name of the file in code where the project logs event was created","type":["string","null"]},"caller_functionname":{"description":"The function in code which created the project logs event","type":["string","null"]},"caller_lineno":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"created":{"description":"The timestamp the project logs event was created","format":"date-time","type":"string"},"error":{"description":"The error that occurred, if any."},"expected":{"description":"The ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. Braintrust currently does not compare `output` to `expected` for you, since there are so many different ways to do that correctly. Instead, these values are just used to help you navigate while digging into analyses. However, we may later use these values to re-score outputs or fine-tune your models."},"facets":{"anyOf":[{"additionalProperties":{"type":["string","null"]},"properties":{},"type":"object"},{"type":"null"}]},"id":{"description":"A unique identifier for the project logs event. If you don't provide one, Braintrust will generate one for you","type":"string"},"input":{"description":"The arguments that uniquely define a user input (an arbitrary, JSON serializable object)."},"is_root":{"description":"Whether this span is a root span","type":["boolean","null"]},"log_id":{"const":"g","description":"A literal 'g' which identifies the log as a project log","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"properties":{"model":{"description":"The model used for this example","type":["string","null"]}},"type":"object"},{"type":"null"}]},"metrics":{"anyOf":[{"additionalProperties":{"type":"number"},"properties":{"caller_filename":{"description":"This metric is deprecated"},"caller_functionname":{"description":"This metric is deprecated"},"caller_lineno":{"description":"This metric is deprecated"},"completion_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"end":{"description":"A unix timestamp recording when the section of code which produced the project logs event finished","type":["number","null"]},"prompt_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"start":{"description":"A unix timestamp recording when the section of code which produced the project logs event started","type":["number","null"]},"tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"org_id":{"description":"Unique id for the organization that the project belongs under","format":"uuid","type":"string"},"origin":{"anyOf":[{"description":"Reference to the original object and event this was copied from.","properties":{"_xact_id":{"description":"Transaction ID of the original event.","type":["string","null"]},"created":{"description":"Created timestamp of the original event. Used to help sort in the UI","type":["string","null"]},"id":{"description":"ID of the original event.","type":"string"},"object_id":{"description":"ID of the object the event is originating from.","format":"uuid","type":"string"},"object_type":{"description":"Type of the object the event is originating from.","enum":["project_logs","experiment","dataset","prompt","function","prompt_session"],"type":"string"}},"required":["object_type","object_id","id"],"type":"object"},{"type":"null"}]},"output":{"description":"The output of your application, including post-processing (an arbitrary, JSON serializable object), that allows you to determine whether the result is correct or not. For example, in an app that generates SQL queries, the `output` should be the _result_ of the SQL query generated by the model, not the query itself, because there may be multiple valid queries that answer a single question."},"project_id":{"description":"Unique identifier for the project","format":"uuid","type":"string"},"root_span_id":{"description":"A unique identifier for the trace this project logs event belongs to","type":"string"},"scores":{"anyOf":[{"additionalProperties":{"anyOf":[{"maximum":1,"minimum":0,"type":"number"},{"type":"null"}]},"properties":{},"type":"object"},{"type":"null"}]},"span_attributes":{"anyOf":[{"additionalProperties":{},"description":"Human-identifying attributes of the span, such as name, type, etc.","properties":{"name":{"description":"Name of the span, for display purposes only","type":["string","null"]},"purpose":{"anyOf":[{"enum":["scorer"],"type":"string"},{"type":"null"}]},"type":{"anyOf":[{"enum":["llm","score","function","eval","task","tool","automation","facet","preprocessor","classifier","review"],"type":"string"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"span_id":{"description":"A unique identifier used to link different project logs events together as part of a full trace. See the [tracing guide](https://www.braintrust.dev/docs/instrument) for full details on tracing","type":"string"},"span_parents":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]}}}},"realtime_state":{"type":"exhausted_timeout","timeout_ms":500,"minimum_xact_id":"1000197707053523103","actual_xact_id":null},"freshness_state":{"last_processed_xact_id":"1000197707053523103","last_considered_xact_id":"1000197707053523103"},"warnings":[]} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9ffc0f72eb30.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9ffc0f72eb30.json new file mode 100644 index 00000000..be9031c8 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-9ffc0f72eb30.json @@ -0,0 +1 @@ +{"data":[],"schema":{"type":"array","items":{"type":"object","properties":{"_async_scoring_state":{},"_pagination_key":{"description":"A stable, time-ordered key that can be used to paginate over project logs events. This field is auto-generated by Braintrust and only exists in Brainstore.","type":["string","null"]},"_xact_id":{"description":"The transaction id of an event is unique to the network operation that processed the event insertion. Transaction ids are monotonically increasing over time and can be used to retrieve a versioned snapshot of the project logs (see the `version` parameter)","type":"string"},"audit_data":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"classifications":{"anyOf":[{"additionalProperties":{"items":{"additionalProperties":false,"properties":{"confidence":{"description":"Optional confidence score for the classification","type":["number","null"]},"id":{"description":"Stable classification identifier","type":"string"},"label":{"description":"Original label of the classification item, which is useful for search and indexing purposes","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"type":"object"},{"type":"null"}],"description":"Optional metadata associated with the classification"},"source":{"anyOf":[{"anyOf":[{"additionalProperties":false,"properties":{"id":{"type":"string"},"type":{"const":"function","type":"string"},"version":{"description":"The version of the function","type":"string"}},"required":["type","id"],"type":"object"},{"additionalProperties":false,"properties":{"function_type":{"default":"scorer","description":"The type of global function. Defaults to 'scorer'.","enum":["llm","scorer","task","tool","custom_view","preprocessor","facet","classifier","tag","parameters","sandbox"],"type":"string"},"name":{"type":"string"},"type":{"const":"global","type":"string"}},"required":["type","name"],"type":"object"}]},{"type":"null"}],"description":"Optional function identifier that produced the classification"}},"required":["id"],"type":"object"},"type":"array"},"properties":{},"type":"object"},{"type":"null"}]},"comments":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"context":{"anyOf":[{"additionalProperties":{},"properties":{"caller_filename":{"description":"Name of the file in code where the project logs event was created","type":["string","null"]},"caller_functionname":{"description":"The function in code which created the project logs event","type":["string","null"]},"caller_lineno":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"created":{"description":"The timestamp the project logs event was created","format":"date-time","type":"string"},"error":{"description":"The error that occurred, if any."},"expected":{"description":"The ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. Braintrust currently does not compare `output` to `expected` for you, since there are so many different ways to do that correctly. Instead, these values are just used to help you navigate while digging into analyses. However, we may later use these values to re-score outputs or fine-tune your models."},"facets":{"anyOf":[{"additionalProperties":{"type":["string","null"]},"properties":{},"type":"object"},{"type":"null"}]},"id":{"description":"A unique identifier for the project logs event. If you don't provide one, Braintrust will generate one for you","type":"string"},"input":{"description":"The arguments that uniquely define a user input (an arbitrary, JSON serializable object)."},"is_root":{"description":"Whether this span is a root span","type":["boolean","null"]},"log_id":{"const":"g","description":"A literal 'g' which identifies the log as a project log","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"properties":{"model":{"description":"The model used for this example","type":["string","null"]}},"type":"object"},{"type":"null"}]},"metrics":{"anyOf":[{"additionalProperties":{"type":"number"},"properties":{"caller_filename":{"description":"This metric is deprecated"},"caller_functionname":{"description":"This metric is deprecated"},"caller_lineno":{"description":"This metric is deprecated"},"completion_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"end":{"description":"A unix timestamp recording when the section of code which produced the project logs event finished","type":["number","null"]},"prompt_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"start":{"description":"A unix timestamp recording when the section of code which produced the project logs event started","type":["number","null"]},"tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"org_id":{"description":"Unique id for the organization that the project belongs under","format":"uuid","type":"string"},"origin":{"anyOf":[{"description":"Reference to the original object and event this was copied from.","properties":{"_xact_id":{"description":"Transaction ID of the original event.","type":["string","null"]},"created":{"description":"Created timestamp of the original event. Used to help sort in the UI","type":["string","null"]},"id":{"description":"ID of the original event.","type":"string"},"object_id":{"description":"ID of the object the event is originating from.","format":"uuid","type":"string"},"object_type":{"description":"Type of the object the event is originating from.","enum":["project_logs","experiment","dataset","prompt","function","prompt_session"],"type":"string"}},"required":["object_type","object_id","id"],"type":"object"},{"type":"null"}]},"output":{"description":"The output of your application, including post-processing (an arbitrary, JSON serializable object), that allows you to determine whether the result is correct or not. For example, in an app that generates SQL queries, the `output` should be the _result_ of the SQL query generated by the model, not the query itself, because there may be multiple valid queries that answer a single question."},"project_id":{"description":"Unique identifier for the project","format":"uuid","type":"string"},"root_span_id":{"description":"A unique identifier for the trace this project logs event belongs to","type":"string"},"scores":{"anyOf":[{"additionalProperties":{"anyOf":[{"maximum":1,"minimum":0,"type":"number"},{"type":"null"}]},"properties":{},"type":"object"},{"type":"null"}]},"span_attributes":{"anyOf":[{"additionalProperties":{},"description":"Human-identifying attributes of the span, such as name, type, etc.","properties":{"name":{"description":"Name of the span, for display purposes only","type":["string","null"]},"purpose":{"anyOf":[{"enum":["scorer"],"type":"string"},{"type":"null"}]},"type":{"anyOf":[{"enum":["llm","score","function","eval","task","tool","automation","facet","preprocessor","classifier","review"],"type":"string"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"span_id":{"description":"A unique identifier used to link different project logs events together as part of a full trace. See the [tracing guide](https://www.braintrust.dev/docs/instrument) for full details on tracing","type":"string"},"span_parents":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]}}}},"realtime_state":{"type":"on","minimum_xact_id":null,"read_bytes":0,"actual_xact_id":null},"freshness_state":{"last_processed_xact_id":"1000197689209569701","last_considered_xact_id":null},"warnings":[]} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-ac7f0143cfa3.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-ac7f0143cfa3.json new file mode 100644 index 00000000..e36ee459 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-ac7f0143cfa3.json @@ -0,0 +1 @@ +{"data":[{"_async_scoring_state":null,"_pagination_key":"p07675102016880246784","_xact_id":"1000197705015669512","audit_data":[{"_xact_id":"1000197705015669512","audit_data":{"action":"upsert"},"metadata":{},"source":"api"}],"classifications":null,"comments":null,"context":{"span_origin":{"instrumentation":{"name":"braintrust-java"},"name":"braintrust.sdk.java","version":"0.3.20-d570ae3-DIRTY"}},"created":"2026-08-17T20:35:36.261Z","error":null,"expected":null,"facets":null,"id":"ce7b9aea749ac037","input":null,"is_root":true,"log_id":"g","metadata":{"braintrust.parent":"project_name:java-unit-test","client":"openai"},"metrics":{"end":1786998940.0545812,"start":1786998936.261514},"org_id":"2732c14b-91d6-4629-901a-c06fcc0df441","origin":null,"output":null,"project_id":"946b2c8d-f5d4-4690-9491-d79e2857c995","root_span_id":"dd5aff9ba63499bef3904fed80e6bc82","scores":null,"span_attributes":{"name":"web_search","type":"task"},"span_id":"ce7b9aea749ac037","span_parents":null,"tags":null},{"_async_scoring_state":null,"_pagination_key":"p07675102016880246786","_xact_id":"1000197705015669512","audit_data":[{"_xact_id":"1000197705015669512","audit_data":{"action":"upsert"},"metadata":{},"source":"api"}],"classifications":null,"comments":null,"context":{"span_origin":{"instrumentation":{"name":"braintrust-java"},"name":"braintrust.sdk.java","version":"0.3.20-d570ae3-DIRTY"}},"created":"2026-08-17T20:35:36.515Z","error":null,"expected":null,"facets":null,"id":"54910acc2ade28ef","input":[{"content":"Search the web for a recent AI news headline and answer in one sentence.","role":"user"}],"is_root":false,"log_id":"g","metadata":{"braintrust.parent":"project_name:java-unit-test","model":"gpt-4o","provider":"openai","request_base_uri":"http://localhost:53703","request_method":"POST","request_path":"responses"},"metrics":{"completion_reasoning_tokens":0,"completion_tokens":73,"end":1786998940.0245836,"prompt_cached_tokens":0,"prompt_tokens":323,"start":1786998936.515887,"tokens":396},"org_id":"2732c14b-91d6-4629-901a-c06fcc0df441","origin":null,"output":[{"action":{"queries":["Search the web for a recent AI news headline and answer in one sentence."],"query":"Search the web for a recent AI news headline and answer in one sentence.","type":"search"},"id":"ws_0911b2daf2cc626d006a837099b31887d08d2d0269a6cd23de","status":"completed","type":"web_search_call"},{"content":[{"annotations":[{"end_index":332,"start_index":233,"title":"New York Post launches \"Hamilton\" AI chatbot","type":"url_citation","url":"https://www.axios.com/2026/08/11/new-york-post-hamilton-ai-chatbot?utm_source=openai"}],"logprobs":[],"text":"The New York Post Media Group has launched \"Hamilton,\" an AI chatbot designed to enhance personalized news experiences, marking the first collaboration between Google Cloud and a North American news outlet to support such a feature. ([axios.com](https://www.axios.com/2026/08/11/new-york-post-hamilton-ai-chatbot?utm_source=openai)) ","type":"output_text"}],"id":"msg_0911b2daf2cc626d006a83709ad25c87d0a02c9444c591bbb4","role":"assistant","status":"completed","type":"message"}],"project_id":"946b2c8d-f5d4-4690-9491-d79e2857c995","root_span_id":"dd5aff9ba63499bef3904fed80e6bc82","scores":null,"span_attributes":{"name":"responses","type":"llm"},"span_id":"54910acc2ade28ef","span_parents":["ce7b9aea749ac037"],"tags":null},{"_async_scoring_state":null,"_pagination_key":"p07675102016880246785","_xact_id":"1000197705015669512","audit_data":[{"_xact_id":"1000197705015669512","audit_data":{"action":"upsert"},"metadata":{},"source":"api"}],"classifications":null,"comments":null,"context":{"span_origin":{"instrumentation":{"name":"braintrust-java"},"name":"braintrust.sdk.java","version":"0.3.20-d570ae3-DIRTY"}},"created":"2026-08-17T20:35:40.023Z","error":null,"expected":null,"facets":null,"id":"55b1b6978ea3fefd","input":{"action":{"queries":["Search the web for a recent AI news headline and answer in one sentence."],"query":"Search the web for a recent AI news headline and answer in one sentence.","type":"search"}},"is_root":false,"log_id":"g","metadata":{"braintrust.parent":"project_name:java-unit-test","status":"completed","tool_id":"ws_0911b2daf2cc626d006a837099b31887d08d2d0269a6cd23de","tool_type":"web_search_call"},"metrics":{"end":1786998940.0232446,"start":1786998940.0230372},"org_id":"2732c14b-91d6-4629-901a-c06fcc0df441","origin":null,"output":{"status":"completed"},"project_id":"946b2c8d-f5d4-4690-9491-d79e2857c995","root_span_id":"dd5aff9ba63499bef3904fed80e6bc82","scores":null,"span_attributes":{"name":"web_search_call","type":"tool"},"span_id":"55b1b6978ea3fefd","span_parents":["54910acc2ade28ef"],"tags":null}],"schema":{"type":"array","items":{"type":"object","properties":{"_async_scoring_state":{},"_pagination_key":{"description":"A stable, time-ordered key that can be used to paginate over project logs events. This field is auto-generated by Braintrust and only exists in Brainstore.","type":["string","null"]},"_xact_id":{"description":"The transaction id of an event is unique to the network operation that processed the event insertion. Transaction ids are monotonically increasing over time and can be used to retrieve a versioned snapshot of the project logs (see the `version` parameter)","type":"string"},"audit_data":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"classifications":{"anyOf":[{"additionalProperties":{"items":{"additionalProperties":false,"properties":{"confidence":{"description":"Optional confidence score for the classification","type":["number","null"]},"id":{"description":"Stable classification identifier","type":"string"},"label":{"description":"Original label of the classification item, which is useful for search and indexing purposes","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"type":"object"},{"type":"null"}],"description":"Optional metadata associated with the classification"},"source":{"anyOf":[{"anyOf":[{"additionalProperties":false,"properties":{"id":{"type":"string"},"type":{"const":"function","type":"string"},"version":{"description":"The version of the function","type":"string"}},"required":["type","id"],"type":"object"},{"additionalProperties":false,"properties":{"function_type":{"default":"scorer","description":"The type of global function. Defaults to 'scorer'.","enum":["llm","scorer","task","tool","custom_view","preprocessor","facet","classifier","tag","parameters","sandbox"],"type":"string"},"name":{"type":"string"},"type":{"const":"global","type":"string"}},"required":["type","name"],"type":"object"}]},{"type":"null"}],"description":"Optional function identifier that produced the classification"}},"required":["id"],"type":"object"},"type":"array"},"properties":{},"type":"object"},{"type":"null"}]},"comments":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"context":{"anyOf":[{"additionalProperties":{},"properties":{"caller_filename":{"description":"Name of the file in code where the project logs event was created","type":["string","null"]},"caller_functionname":{"description":"The function in code which created the project logs event","type":["string","null"]},"caller_lineno":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"created":{"description":"The timestamp the project logs event was created","format":"date-time","type":"string"},"error":{"description":"The error that occurred, if any."},"expected":{"description":"The ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. Braintrust currently does not compare `output` to `expected` for you, since there are so many different ways to do that correctly. Instead, these values are just used to help you navigate while digging into analyses. However, we may later use these values to re-score outputs or fine-tune your models."},"facets":{"anyOf":[{"additionalProperties":{"type":["string","null"]},"properties":{},"type":"object"},{"type":"null"}]},"id":{"description":"A unique identifier for the project logs event. If you don't provide one, Braintrust will generate one for you","type":"string"},"input":{"description":"The arguments that uniquely define a user input (an arbitrary, JSON serializable object)."},"is_root":{"description":"Whether this span is a root span","type":["boolean","null"]},"log_id":{"const":"g","description":"A literal 'g' which identifies the log as a project log","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"properties":{"model":{"description":"The model used for this example","type":["string","null"]}},"type":"object"},{"type":"null"}]},"metrics":{"anyOf":[{"additionalProperties":{"type":"number"},"properties":{"caller_filename":{"description":"This metric is deprecated"},"caller_functionname":{"description":"This metric is deprecated"},"caller_lineno":{"description":"This metric is deprecated"},"completion_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"end":{"description":"A unix timestamp recording when the section of code which produced the project logs event finished","type":["number","null"]},"prompt_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"start":{"description":"A unix timestamp recording when the section of code which produced the project logs event started","type":["number","null"]},"tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"org_id":{"description":"Unique id for the organization that the project belongs under","format":"uuid","type":"string"},"origin":{"anyOf":[{"description":"Reference to the original object and event this was copied from.","properties":{"_xact_id":{"description":"Transaction ID of the original event.","type":["string","null"]},"created":{"description":"Created timestamp of the original event. Used to help sort in the UI","type":["string","null"]},"id":{"description":"ID of the original event.","type":"string"},"object_id":{"description":"ID of the object the event is originating from.","format":"uuid","type":"string"},"object_type":{"description":"Type of the object the event is originating from.","enum":["project_logs","experiment","dataset","prompt","function","prompt_session"],"type":"string"}},"required":["object_type","object_id","id"],"type":"object"},{"type":"null"}]},"output":{"description":"The output of your application, including post-processing (an arbitrary, JSON serializable object), that allows you to determine whether the result is correct or not. For example, in an app that generates SQL queries, the `output` should be the _result_ of the SQL query generated by the model, not the query itself, because there may be multiple valid queries that answer a single question."},"project_id":{"description":"Unique identifier for the project","format":"uuid","type":"string"},"root_span_id":{"description":"A unique identifier for the trace this project logs event belongs to","type":"string"},"scores":{"anyOf":[{"additionalProperties":{"anyOf":[{"maximum":1,"minimum":0,"type":"number"},{"type":"null"}]},"properties":{},"type":"object"},{"type":"null"}]},"span_attributes":{"anyOf":[{"additionalProperties":{},"description":"Human-identifying attributes of the span, such as name, type, etc.","properties":{"name":{"description":"Name of the span, for display purposes only","type":["string","null"]},"purpose":{"anyOf":[{"enum":["scorer"],"type":"string"},{"type":"null"}]},"type":{"anyOf":[{"enum":["llm","score","function","eval","task","tool","automation","facet","preprocessor","classifier","review"],"type":"string"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"span_id":{"description":"A unique identifier used to link different project logs events together as part of a full trace. See the [tracing guide](https://www.braintrust.dev/docs/instrument) for full details on tracing","type":"string"},"span_parents":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]}}}},"realtime_state":{"type":"on","minimum_xact_id":"1000197689209569701","read_bytes":3766,"actual_xact_id":"1000197705015669512"},"freshness_state":{"last_processed_xact_id":"1000197705015669512","last_considered_xact_id":"1000197705015669512"},"warnings":[]} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-bce8572f7d22.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-bce8572f7d22.json new file mode 100644 index 00000000..8f1aa200 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/__files/btql-bce8572f7d22.json @@ -0,0 +1 @@ +{"data":[],"schema":{"type":"array","items":{"type":"object","properties":{"_async_scoring_state":{},"_pagination_key":{"description":"A stable, time-ordered key that can be used to paginate over project logs events. This field is auto-generated by Braintrust and only exists in Brainstore.","type":["string","null"]},"_xact_id":{"description":"The transaction id of an event is unique to the network operation that processed the event insertion. Transaction ids are monotonically increasing over time and can be used to retrieve a versioned snapshot of the project logs (see the `version` parameter)","type":"string"},"audit_data":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"classifications":{"anyOf":[{"additionalProperties":{"items":{"additionalProperties":false,"properties":{"confidence":{"description":"Optional confidence score for the classification","type":["number","null"]},"id":{"description":"Stable classification identifier","type":"string"},"label":{"description":"Original label of the classification item, which is useful for search and indexing purposes","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"type":"object"},{"type":"null"}],"description":"Optional metadata associated with the classification"},"source":{"anyOf":[{"anyOf":[{"additionalProperties":false,"properties":{"id":{"type":"string"},"type":{"const":"function","type":"string"},"version":{"description":"The version of the function","type":"string"}},"required":["type","id"],"type":"object"},{"additionalProperties":false,"properties":{"function_type":{"default":"scorer","description":"The type of global function. Defaults to 'scorer'.","enum":["llm","scorer","task","tool","custom_view","preprocessor","facet","classifier","tag","parameters","sandbox"],"type":"string"},"name":{"type":"string"},"type":{"const":"global","type":"string"}},"required":["type","name"],"type":"object"}]},{"type":"null"}],"description":"Optional function identifier that produced the classification"}},"required":["id"],"type":"object"},"type":"array"},"properties":{},"type":"object"},{"type":"null"}]},"comments":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}]},"context":{"anyOf":[{"additionalProperties":{},"properties":{"caller_filename":{"description":"Name of the file in code where the project logs event was created","type":["string","null"]},"caller_functionname":{"description":"The function in code which created the project logs event","type":["string","null"]},"caller_lineno":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"created":{"description":"The timestamp the project logs event was created","format":"date-time","type":"string"},"error":{"description":"The error that occurred, if any."},"expected":{"description":"The ground truth value (an arbitrary, JSON serializable object) that you'd compare to `output` to determine if your `output` value is correct or not. Braintrust currently does not compare `output` to `expected` for you, since there are so many different ways to do that correctly. Instead, these values are just used to help you navigate while digging into analyses. However, we may later use these values to re-score outputs or fine-tune your models."},"facets":{"anyOf":[{"additionalProperties":{"type":["string","null"]},"properties":{},"type":"object"},{"type":"null"}]},"id":{"description":"A unique identifier for the project logs event. If you don't provide one, Braintrust will generate one for you","type":"string"},"input":{"description":"The arguments that uniquely define a user input (an arbitrary, JSON serializable object)."},"is_root":{"description":"Whether this span is a root span","type":["boolean","null"]},"log_id":{"const":"g","description":"A literal 'g' which identifies the log as a project log","type":"string"},"metadata":{"anyOf":[{"additionalProperties":{},"properties":{"model":{"description":"The model used for this example","type":["string","null"]}},"type":"object"},{"type":"null"}]},"metrics":{"anyOf":[{"additionalProperties":{"type":"number"},"properties":{"caller_filename":{"description":"This metric is deprecated"},"caller_functionname":{"description":"This metric is deprecated"},"caller_lineno":{"description":"This metric is deprecated"},"completion_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"end":{"description":"A unix timestamp recording when the section of code which produced the project logs event finished","type":["number","null"]},"prompt_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]},"start":{"description":"A unix timestamp recording when the section of code which produced the project logs event started","type":["number","null"]},"tokens":{"anyOf":[{"type":"integer"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"org_id":{"description":"Unique id for the organization that the project belongs under","format":"uuid","type":"string"},"origin":{"anyOf":[{"description":"Reference to the original object and event this was copied from.","properties":{"_xact_id":{"description":"Transaction ID of the original event.","type":["string","null"]},"created":{"description":"Created timestamp of the original event. Used to help sort in the UI","type":["string","null"]},"id":{"description":"ID of the original event.","type":"string"},"object_id":{"description":"ID of the object the event is originating from.","format":"uuid","type":"string"},"object_type":{"description":"Type of the object the event is originating from.","enum":["project_logs","experiment","dataset","prompt","function","prompt_session"],"type":"string"}},"required":["object_type","object_id","id"],"type":"object"},{"type":"null"}]},"output":{"description":"The output of your application, including post-processing (an arbitrary, JSON serializable object), that allows you to determine whether the result is correct or not. For example, in an app that generates SQL queries, the `output` should be the _result_ of the SQL query generated by the model, not the query itself, because there may be multiple valid queries that answer a single question."},"project_id":{"description":"Unique identifier for the project","format":"uuid","type":"string"},"root_span_id":{"description":"A unique identifier for the trace this project logs event belongs to","type":"string"},"scores":{"anyOf":[{"additionalProperties":{"anyOf":[{"maximum":1,"minimum":0,"type":"number"},{"type":"null"}]},"properties":{},"type":"object"},{"type":"null"}]},"span_attributes":{"anyOf":[{"additionalProperties":{},"description":"Human-identifying attributes of the span, such as name, type, etc.","properties":{"name":{"description":"Name of the span, for display purposes only","type":["string","null"]},"purpose":{"anyOf":[{"enum":["scorer"],"type":"string"},{"type":"null"}]},"type":{"anyOf":[{"enum":["llm","score","function","eval","task","tool","automation","facet","preprocessor","classifier","review"],"type":"string"},{"type":"null"}]}},"type":"object"},{"type":"null"}]},"span_id":{"description":"A unique identifier used to link different project logs events together as part of a full trace. See the [tracing guide](https://www.braintrust.dev/docs/instrument) for full details on tracing","type":"string"},"span_parents":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]}}}},"realtime_state":{"type":"on","minimum_xact_id":null,"read_bytes":0,"actual_xact_id":null},"freshness_state":{"last_processed_xact_id":"1000197707049669837","last_considered_xact_id":null},"warnings":[]} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9744b37774d9.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9744b37774d9.json new file mode 100644 index 00000000..fe40a91d --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9744b37774d9.json @@ -0,0 +1,45 @@ +{ + "id" : "eddb6625-e650-372b-91fa-7745a18c0af7", + "name" : "btql", + "request" : { + "url" : "/btql", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"query\":\"SELECT * FROM project_logs('946b2c8d-f5d4-4690-9491-d79e2857c995') WHERE root_span_id = '3dc06fd1dbdf5809ad38fa34d7c94014' ORDER BY created ASC LIMIT 1000\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "btql-9744b37774d9.json", + "headers" : { + "X-Cache" : "Miss from cloudfront", + "x-amz-apigw-id" : "CSGEJGENIAMEWfw=", + "vary" : "Origin", + "X-Amz-Cf-Pop" : [ "SEA900-P1", "SEA900-P10" ], + "x-bt-brainstore-duration-ms" : "829", + "X-Amzn-Trace-Id" : "Root=1-6a83ea1a-676873d16b1365e33484c3a8;Parent=3c81df77dccbfb12;Sampled=0;Lineage=1:24be3d11:0", + "x-bt-api-duration-ms" : "898", + "Date" : "Tue, 18 Aug 2026 05:14:03 GMT", + "Via" : "1.1 4cb8a7f3f7a5d9d545889e0d3926b9c2.cloudfront.net (CloudFront), 1.1 74e8c76139b8c7f9b11d5e4441c2a7a2.cloudfront.net (CloudFront)", + "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan,x-bt-api-duration-ms,x-bt-brainstore-duration-ms,x-bt-internal-trace-id,x-bt-error-origin,x-bt-used-endpoint,x-bt-overflow-url", + "access-control-allow-credentials" : "true", + "x-bt-internal-trace-id" : "6a83ea1a0000000013485eb2ce030fa4", + "x-amzn-RequestId" : "5e800314-fd1a-4475-b313-dbcabe73fd8f", + "X-Amz-Cf-Id" : "tOnMfY9Xq_3txHo7Mc95Hm6dyMjdk9nmCLEuaj3YeSHpbO4qCE5IFA==", + "cache-control" : "private, no-cache", + "Content-Type" : "application/json" + } + }, + "uuid" : "eddb6625-e650-372b-91fa-7745a18c0af7", + "persistent" : true, + "scenarioName" : "scenario-1-btql", + "requiredScenarioState" : "scenario-1-btql-2", + "insertionIndex" : 184 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9ffc0f72eb30.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9ffc0f72eb30.json new file mode 100644 index 00000000..0eb1c628 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-9ffc0f72eb30.json @@ -0,0 +1,46 @@ +{ + "id" : "d8138837-5abb-372e-bbc1-a7c02eacd516", + "name" : "btql", + "request" : { + "url" : "/btql", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"query\":\"SELECT * FROM project_logs('946b2c8d-f5d4-4690-9491-d79e2857c995') WHERE root_span_id = 'dd5aff9ba63499bef3904fed80e6bc82' ORDER BY created ASC LIMIT 1000\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "btql-9ffc0f72eb30.json", + "headers" : { + "X-Cache" : "Miss from cloudfront", + "x-amz-apigw-id" : "CQ6IfEY5IAMEKUQ=", + "vary" : "Origin", + "X-Amz-Cf-Pop" : [ "SEA900-P1", "SEA900-P10" ], + "x-bt-brainstore-duration-ms" : "46", + "X-Amzn-Trace-Id" : "Root=1-6a83709c-33945ff84ae7ee492bd0efe7;Parent=616d2d9a9a34f776;Sampled=0;Lineage=1:24be3d11:0", + "x-bt-api-duration-ms" : "196", + "Date" : "Mon, 17 Aug 2026 20:35:40 GMT", + "Via" : "1.1 b47176981720d8607d309e56e9510316.cloudfront.net (CloudFront), 1.1 0df7f27a01014ab815259ca2d88193c6.cloudfront.net (CloudFront)", + "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan,x-bt-api-duration-ms,x-bt-brainstore-duration-ms,x-bt-internal-trace-id,x-bt-error-origin,x-bt-used-endpoint,x-bt-overflow-url", + "access-control-allow-credentials" : "true", + "x-bt-internal-trace-id" : "6a83709c0000000079d72810811e6dc6", + "x-amzn-RequestId" : "64316489-7673-4924-8ecc-7119256cfaf1", + "X-Amz-Cf-Id" : "dQLWhPEbl-w3OREHDxrBTRzeONLUQJT1BOQ_mqdEX96_-RVzjQoEHw==", + "cache-control" : "private, no-cache", + "Content-Type" : "application/json" + } + }, + "uuid" : "d8138837-5abb-372e-bbc1-a7c02eacd516", + "persistent" : true, + "scenarioName" : "scenario-1-btql", + "requiredScenarioState" : "Started", + "newScenarioState" : "scenario-1-btql-2", + "insertionIndex" : 183 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-ac7f0143cfa3.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-ac7f0143cfa3.json new file mode 100644 index 00000000..e0bc9fa4 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-ac7f0143cfa3.json @@ -0,0 +1,45 @@ +{ + "id" : "8f9af76d-a446-3fdb-9e6b-6639a1e0a8e8", + "name" : "btql", + "request" : { + "url" : "/btql", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"query\":\"SELECT * FROM project_logs('946b2c8d-f5d4-4690-9491-d79e2857c995') WHERE root_span_id = 'dd5aff9ba63499bef3904fed80e6bc82' ORDER BY created ASC LIMIT 1000\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "btql-ac7f0143cfa3.json", + "headers" : { + "X-Cache" : "Miss from cloudfront", + "x-amz-apigw-id" : "CQ6JZHEyoAMEQpA=", + "vary" : "Origin", + "X-Amz-Cf-Pop" : [ "SEA900-P1", "SEA900-P10" ], + "x-bt-brainstore-duration-ms" : "398", + "X-Amzn-Trace-Id" : "Root=1-6a8370a2-029663b14a65881f17ff2a7a;Parent=2725ac71f9c90549;Sampled=0;Lineage=1:24be3d11:0", + "x-bt-api-duration-ms" : "477", + "Date" : "Mon, 17 Aug 2026 20:35:46 GMT", + "Via" : "1.1 59e4792b9d6184bfa491a317b36590d2.cloudfront.net (CloudFront), 1.1 d525041695bdb6325f78ebba5c11b8a2.cloudfront.net (CloudFront)", + "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan,x-bt-api-duration-ms,x-bt-brainstore-duration-ms,x-bt-internal-trace-id,x-bt-error-origin,x-bt-used-endpoint,x-bt-overflow-url", + "access-control-allow-credentials" : "true", + "x-bt-internal-trace-id" : "6a8370a2000000007aacf1d0c95016f2", + "x-amzn-RequestId" : "3d121291-f17b-49dd-8099-e0cbf10e455c", + "X-Amz-Cf-Id" : "ChRFQG6EOCGpNzO3hVOc3wjdUOoyjiVVGQxckHBGLq3t8lT9Pqg1EA==", + "cache-control" : "private, no-cache", + "Content-Type" : "application/json" + } + }, + "uuid" : "8f9af76d-a446-3fdb-9e6b-6639a1e0a8e8", + "persistent" : true, + "scenarioName" : "scenario-1-btql", + "requiredScenarioState" : "scenario-1-btql-2", + "insertionIndex" : 182 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-bce8572f7d22.json b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-bce8572f7d22.json new file mode 100644 index 00000000..4c45a7f6 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/braintrust/mappings/btql-bce8572f7d22.json @@ -0,0 +1,46 @@ +{ + "id" : "f0072d8f-573d-334e-96c0-8961d0da4543", + "name" : "btql", + "request" : { + "url" : "/btql", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"query\":\"SELECT * FROM project_logs('946b2c8d-f5d4-4690-9491-d79e2857c995') WHERE root_span_id = '3dc06fd1dbdf5809ad38fa34d7c94014' ORDER BY created ASC LIMIT 1000\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "btql-bce8572f7d22.json", + "headers" : { + "X-Cache" : "Miss from cloudfront", + "x-amz-apigw-id" : "CSGDTGdWIAMEITQ=", + "vary" : "Origin", + "X-Amz-Cf-Pop" : [ "SEA900-P1", "SEA900-P10" ], + "x-bt-brainstore-duration-ms" : "42", + "X-Amzn-Trace-Id" : "Root=1-6a83ea14-636657db7bdbbc8a2eecf080;Parent=5aff695cacddc0eb;Sampled=0;Lineage=1:24be3d11:0", + "x-bt-api-duration-ms" : "112", + "Date" : "Tue, 18 Aug 2026 05:13:56 GMT", + "Via" : "1.1 db84db36e16ca0c80b0992006d731900.cloudfront.net (CloudFront), 1.1 d525041695bdb6325f78ebba5c11b8a2.cloudfront.net (CloudFront)", + "access-control-expose-headers" : "x-bt-cursor,x-bt-found-existing,x-bt-query-plan,x-bt-api-duration-ms,x-bt-brainstore-duration-ms,x-bt-internal-trace-id,x-bt-error-origin,x-bt-used-endpoint,x-bt-overflow-url", + "access-control-allow-credentials" : "true", + "x-bt-internal-trace-id" : "6a83ea140000000043065e2f97357f3f", + "x-amzn-RequestId" : "e59b1ae0-2914-40cc-9a1b-bca90eb93d86", + "X-Amz-Cf-Id" : "mBhcxejtDaJ9dAe7xbuNsDClEWssbkJv6TvC2q7fY5k234ajirfvDA==", + "cache-control" : "private, no-cache", + "Content-Type" : "application/json" + } + }, + "uuid" : "f0072d8f-573d-334e-96c0-8961d0da4543", + "persistent" : true, + "scenarioName" : "scenario-1-btql", + "requiredScenarioState" : "Started", + "newScenarioState" : "scenario-1-btql-2", + "insertionIndex" : 185 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7c0db28da6b8.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7c0db28da6b8.json new file mode 100644 index 00000000..8f664dfa --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-7c0db28da6b8.json @@ -0,0 +1,35 @@ +{ + "id": "chatcmpl-ECe43IuPfU77adv4loq6PNdopaMx2", + "object": "chat.completion", + "created": 1786682651, + "model": "o4-mini-2025-04-16", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Each term is the product of two consecutive integers. In fact\n\n a₁=1·2=2, \n a₂=2·3=6, \n a₃=3·4=12, \n a₄=4·5=20, \n a₅=5·6=30, …\n\nso for n≥1\n\n aₙ = n·(n+1)\n\nEquivalently aₙ = n² + n.", + "refusal": null, + "annotations": [] + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 41, + "completion_tokens": 826, + "total_tokens": 867, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 704, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": null +} diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-b5bcb38ce8df.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-b5bcb38ce8df.json new file mode 100644 index 00000000..2e872a27 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/chat_completions-b5bcb38ce8df.json @@ -0,0 +1,35 @@ +{ + "id": "chatcmpl-ECe43NIjWyAmKELxrwXrmNLta5t8z", + "object": "chat.completion", + "created": 1786682651, + "model": "o4-mini-2025-04-16", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Each term is the product of two consecutive positive integers:\n\nTerm 1: 1×2 = 2 \nTerm 2: 2×3 = 6 \nTerm 3: 3×4 = 12 \nTerm 4: 4×5 = 20 \nTerm 5: 5×6 = 30 \n\nIf we call the nth term aₙ (with n starting at 1), then\n\n aₙ = n·(n + 1)\n\nEquivalently,\n\n aₙ = n² + n.", + "refusal": null, + "annotations": [] + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 41, + "completion_tokens": 525, + "total_tokens": 566, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 384, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": null +} diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-0c37df81d2cf.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-0c37df81d2cf.json new file mode 100644 index 00000000..efd3c7cb --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-0c37df81d2cf.json @@ -0,0 +1,127 @@ +{ + "id": "resp_050cff5ce90128c7006a7fc279ddc487d08d20eb4e7cbf2ae6", + "object": "response", + "created_at": 1786757753, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1786757760, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-4o-2024-08-06", + "moderation": null, + "output": [ + { + "id": "ws_050cff5ce90128c7006a7fc27d7c8c87d0919a4bacb3b2679e", + "type": "web_search_call", + "status": "completed", + "action": { + "type": "search", + "queries": [ + "recent news about artificial intelligence" + ], + "query": "recent news about artificial intelligence" + } + }, + { + "id": "msg_050cff5ce90128c7006a7fc27f26a887d0ae49e55d9edeb9f2", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [ + { + "type": "url_citation", + "end_index": 493, + "start_index": 392, + "title": "2025 in artificial intelligence", + "url": "https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai" + } + ], + "logprobs": [], + "text": "As of August 2026, a notable development in artificial intelligence is the release of OpenAI's GPT-5 on August 8, 2025. According to OpenAI CEO Sam Altman, GPT-5 is designed to provide PhD-level expertise. However, it has faced challenges with basic tasks such as spelling and geography, and it cannot continuously learn. Additionally, GPT-5 scores lower than Grok-4 on Humanity's Last Exam. ([en.wikipedia.org](https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai)) " + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "default", + "store": true, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": "auto", + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 1 + } + }, + "tools": [ + { + "type": "web_search_preview", + "search_content_types": [ + "text" + ], + "search_context_size": "medium", + "user_location": { + "type": "approximate", + "city": null, + "country": "US", + "region": null, + "timezone": null + } + } + ], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 314, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 134, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 448 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-31d9a6eee624.txt b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-31d9a6eee624.txt new file mode 100644 index 00000000..84aff527 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-31d9a6eee624.txt @@ -0,0 +1,315 @@ +event: response.created +data: {"type":"response.created","response":{"id":"resp_0dddc08e04ed0eef006a7fc272fa6c819a97e0ce827ab581a3","object":"response","created_at":1786757747,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + +event: response.in_progress +data: {"type":"response.in_progress","response":{"id":"resp_0dddc08e04ed0eef006a7fc272fa6c819a97e0ce827ab581a3","object":"response","created_at":1786757747,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + +event: response.output_item.added +data: {"type":"response.output_item.added","item":{"id":"ws_0dddc08e04ed0eef006a7fc276c298819a835576df99f28ffb","type":"web_search_call","status":"in_progress","action":{"type":"search"}},"output_index":0,"sequence_number":2} + +event: response.web_search_call.in_progress +data: {"type":"response.web_search_call.in_progress","item_id":"ws_0dddc08e04ed0eef006a7fc276c298819a835576df99f28ffb","output_index":0,"sequence_number":3} + +event: response.web_search_call.searching +data: {"type":"response.web_search_call.searching","item_id":"ws_0dddc08e04ed0eef006a7fc276c298819a835576df99f28ffb","output_index":0,"sequence_number":4} + +event: response.web_search_call.completed +data: {"type":"response.web_search_call.completed","item_id":"ws_0dddc08e04ed0eef006a7fc276c298819a835576df99f28ffb","output_index":0,"sequence_number":5} + +event: response.output_item.done +data: {"type":"response.output_item.done","item":{"id":"ws_0dddc08e04ed0eef006a7fc276c298819a835576df99f28ffb","type":"web_search_call","status":"completed","action":{"type":"search","queries":["recent headline about artificial intelligence"],"query":"recent headline about artificial intelligence"}},"output_index":0,"sequence_number":6} + +event: response.output_item.added +data: {"type":"response.output_item.added","item":{"id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":1,"sequence_number":7} + +event: response.content_part.added +data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":8} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"As","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"p4iQfM4F2I53x1","output_index":1,"sequence_number":9} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"GyPNTZe5a7NDH","output_index":1,"sequence_number":10} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" August","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"XrzRFgSSO","output_index":1,"sequence_number":11} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"kIqSanYCBDRc86O","output_index":1,"sequence_number":12} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"202","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"Ay77x92qZGltt","output_index":1,"sequence_number":13} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"6","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"8pvLFQgfF0H9Xld","output_index":1,"sequence_number":14} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"0YOyEccpeB120pF","output_index":1,"sequence_number":15} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" a","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"2HcykqUAcpiNuh","output_index":1,"sequence_number":16} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" notable","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"utTGr5al","output_index":1,"sequence_number":17} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" development","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"qb3V","output_index":1,"sequence_number":18} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" in","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"C1I5IgBCS9t5V","output_index":1,"sequence_number":19} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" artificial","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"aucNp","output_index":1,"sequence_number":20} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" intelligence","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"ZBe","output_index":1,"sequence_number":21} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" is","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"kYcbIbwDnwN9x","output_index":1,"sequence_number":22} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"MLFzNkDDOSdv","output_index":1,"sequence_number":23} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" release","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"78OGYLoC","output_index":1,"sequence_number":24} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"OgYLbuaqsHcF9","output_index":1,"sequence_number":25} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Open","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"rilvox8JPb8","output_index":1,"sequence_number":26} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"AI","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"oPxb5svp4sAeyw","output_index":1,"sequence_number":27} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"'s","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"ZnF5O64CEgwAiq","output_index":1,"sequence_number":28} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" GPT","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"8MRKcz8iCYR6","output_index":1,"sequence_number":29} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"MXIpsXsT4GIRXQJ","output_index":1,"sequence_number":30} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"5","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"ak22ZoyUw4v3sFk","output_index":1,"sequence_number":31} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" on","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"rEyPABRrS0A4z","output_index":1,"sequence_number":32} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" August","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"mUezIJydq","output_index":1,"sequence_number":33} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"PTEWWbc7Ngnq8N2","output_index":1,"sequence_number":34} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"8","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"bc5nrccDmFZ8Wcw","output_index":1,"sequence_number":35} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"gtdq2ureC8QOvBu","output_index":1,"sequence_number":36} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"OS5yEdaf17bmJpS","output_index":1,"sequence_number":37} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"202","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"naDma9Mcp85aA","output_index":1,"sequence_number":38} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"5","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"LXbhAMN4O1myfQV","output_index":1,"sequence_number":39} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"N0nfKYosZKT2mZ0","output_index":1,"sequence_number":40} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"zcO9rPzUeRuFLwG","output_index":1,"sequence_number":41} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"According","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"zkPnirZ","output_index":1,"sequence_number":42} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"ctaPSl5v0AOgW","output_index":1,"sequence_number":43} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Open","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"WVWyk93tP0y","output_index":1,"sequence_number":44} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"AI","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"bGJMHk3w8ne2fp","output_index":1,"sequence_number":45} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" CEO","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"kNRuFNc0NTzo","output_index":1,"sequence_number":46} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Sam","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"vFtK7W3csUkK","output_index":1,"sequence_number":47} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Alt","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"lpn7nr7nstC9","output_index":1,"sequence_number":48} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"man","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"FruMXyLIaSeys","output_index":1,"sequence_number":49} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"fPIakSosAHxeCz5","output_index":1,"sequence_number":50} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" GPT","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"keAIx3PgsfZE","output_index":1,"sequence_number":51} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"J6CTvqT4orCKpca","output_index":1,"sequence_number":52} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"5","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"ekNf9KB0d4mGeju","output_index":1,"sequence_number":53} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" is","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"AWq3Ybj63KKK1","output_index":1,"sequence_number":54} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" designed","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"6LMw6f3","output_index":1,"sequence_number":55} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"tw9KPbMWmOG0m","output_index":1,"sequence_number":56} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" provide","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"hmO3iq4M","output_index":1,"sequence_number":57} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Ph","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"puGg2CqytCgzE","output_index":1,"sequence_number":58} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"D","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"azbfYCVSjwgmPpk","output_index":1,"sequence_number":59} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-level","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"BBPquq7N8W","output_index":1,"sequence_number":60} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" expertise","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"4Dm0QN","output_index":1,"sequence_number":61} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"cgRkWV85SovW3AI","output_index":1,"sequence_number":62} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"B0pppZVMmoKT8LK","output_index":1,"sequence_number":63} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"However","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"7GJm1DJlo","output_index":1,"sequence_number":64} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"IiaNduaCAgHdPp9","output_index":1,"sequence_number":65} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" it","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"yWhjIFhnjVOkf","output_index":1,"sequence_number":66} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" has","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"QgFp1oyP81W1","output_index":1,"sequence_number":67} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" faced","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"7EZ47PxKs5","output_index":1,"sequence_number":68} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" challenges","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"8Xllj","output_index":1,"sequence_number":69} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" with","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"CrqQNaFCNfj","output_index":1,"sequence_number":70} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" basic","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"LbOMS5CH1a","output_index":1,"sequence_number":71} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" tasks","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"eB7svHO6yR","output_index":1,"sequence_number":72} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" such","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"NE3WbfpeThM","output_index":1,"sequence_number":73} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" as","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"E48VJwCEHM38W","output_index":1,"sequence_number":74} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" spelling","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"nhXx9ij","output_index":1,"sequence_number":75} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"FinC3Ho9uh1A","output_index":1,"sequence_number":76} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" geography","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"8MZR2l","output_index":1,"sequence_number":77} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"0V4WYtRZjhh3q1A","output_index":1,"sequence_number":78} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" cannot","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"nn8b1DBij","output_index":1,"sequence_number":79} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" continuously","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"OyE","output_index":1,"sequence_number":80} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" learn","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"LE5Ncno4vq","output_index":1,"sequence_number":81} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"BY9koCdsY5iql2d","output_index":1,"sequence_number":82} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"uSyW4yXmbgRc","output_index":1,"sequence_number":83} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" scores","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"Y9mSdUoUx","output_index":1,"sequence_number":84} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" lower","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"4ZP7WylMAy","output_index":1,"sequence_number":85} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" than","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"BjOfLgq8Fej","output_index":1,"sequence_number":86} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Gro","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"pCUaWZohC16Y","output_index":1,"sequence_number":87} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"k","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"Xz6Ude073ndfoMo","output_index":1,"sequence_number":88} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"-","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"rZ4c0naVSCLtF8o","output_index":1,"sequence_number":89} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"4","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"Tn8ANS6WLtOjDlx","output_index":1,"sequence_number":90} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" on","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"GX4nftA8B8sFk","output_index":1,"sequence_number":91} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Humanity","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"WCsVI0t","output_index":1,"sequence_number":92} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"'s","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"jL4nv4KLTUkgf2","output_index":1,"sequence_number":93} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Last","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"4Upt4GZubsA","output_index":1,"sequence_number":94} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" Exam","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"QbqrzTrVfbx","output_index":1,"sequence_number":95} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"2fUWX8SyOBDFGKl","output_index":1,"sequence_number":96} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"fQ1KKp0qBGeu5np","output_index":1,"sequence_number":97} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":"([en.wikipedia.org](https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai))","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"tn0wd1lHZiq","output_index":1,"sequence_number":98} + +event: response.output_text.annotation.added +data: {"type":"response.output_text.annotation.added","annotation":{"type":"url_citation","end_index":470,"start_index":369,"title":"2025 in artificial intelligence","url":"https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai"},"annotation_index":0,"content_index":0,"item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","output_index":1,"sequence_number":99} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"obfuscation":"rccxILlSOtR6D4d","output_index":1,"sequence_number":100} + +event: response.output_text.done +data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","logprobs":[],"output_index":1,"sequence_number":101,"text":"As of August 2026, a notable development in artificial intelligence is the release of OpenAI's GPT-5 on August 8, 2025. According to OpenAI CEO Sam Altman, GPT-5 is designed to provide PhD-level expertise. However, it has faced challenges with basic tasks such as spelling and geography, cannot continuously learn, and scores lower than Grok-4 on Humanity's Last Exam. ([en.wikipedia.org](https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai)) "} + +event: response.content_part.done +data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","output_index":1,"part":{"type":"output_text","annotations":[{"type":"url_citation","end_index":470,"start_index":369,"title":"2025 in artificial intelligence","url":"https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai"}],"logprobs":[],"text":"As of August 2026, a notable development in artificial intelligence is the release of OpenAI's GPT-5 on August 8, 2025. According to OpenAI CEO Sam Altman, GPT-5 is designed to provide PhD-level expertise. However, it has faced challenges with basic tasks such as spelling and geography, cannot continuously learn, and scores lower than Grok-4 on Humanity's Last Exam. ([en.wikipedia.org](https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai)) "},"sequence_number":102} + +event: response.output_item.done +data: {"type":"response.output_item.done","item":{"id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[{"type":"url_citation","end_index":470,"start_index":369,"title":"2025 in artificial intelligence","url":"https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai"}],"logprobs":[],"text":"As of August 2026, a notable development in artificial intelligence is the release of OpenAI's GPT-5 on August 8, 2025. According to OpenAI CEO Sam Altman, GPT-5 is designed to provide PhD-level expertise. However, it has faced challenges with basic tasks such as spelling and geography, cannot continuously learn, and scores lower than Grok-4 on Humanity's Last Exam. ([en.wikipedia.org](https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai)) "}],"role":"assistant"},"output_index":1,"sequence_number":103} + +event: response.completed +data: {"type":"response.completed","response":{"id":"resp_0dddc08e04ed0eef006a7fc272fa6c819a97e0ce827ab581a3","object":"response","created_at":1786757747,"status":"completed","background":false,"completed_at":1786757753,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-2024-08-06","moderation":null,"output":[{"id":"ws_0dddc08e04ed0eef006a7fc276c298819a835576df99f28ffb","type":"web_search_call","status":"completed","action":{"type":"search","queries":["recent headline about artificial intelligence"],"query":"recent headline about artificial intelligence"}},{"id":"msg_0dddc08e04ed0eef006a7fc277c2d0819aa2b460e1b35c087b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[{"type":"url_citation","end_index":470,"start_index":369,"title":"2025 in artificial intelligence","url":"https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai"}],"logprobs":[],"text":"As of August 2026, a notable development in artificial intelligence is the release of OpenAI's GPT-5 on August 8, 2025. According to OpenAI CEO Sam Altman, GPT-5 is designed to provide PhD-level expertise. However, it has faced challenges with basic tasks such as spelling and geography, cannot continuously learn, and scores lower than Grok-4 on Humanity's Last Exam. ([en.wikipedia.org](https://en.wikipedia.org/wiki/2025_in_artificial_intelligence?utm_source=openai)) "}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":1}},"tools":[{"type":"web_search_preview","search_content_types":["text"],"search_context_size":"medium","user_location":{"type":"approximate","city":null,"country":"US","region":null,"timezone":null}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":314,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":129,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":443},"user":null,"metadata":{}},"sequence_number":104} + diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-d93cfca55488.json b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-d93cfca55488.json new file mode 100644 index 00000000..82959c48 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/__files/responses-d93cfca55488.json @@ -0,0 +1,129 @@ +{ + "id": "resp_0911b2daf2cc626d006a837099849887d0a840ba688fbe7808", + "object": "response", + "created_at": 1786998937, + "status": "completed", + "background": false, + "billing": { + "payer": "developer" + }, + "completed_at": 1786998939, + "error": null, + "frequency_penalty": 0.0, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "max_tool_calls": null, + "model": "gpt-4o-2024-08-06", + "moderation": null, + "output": [ + { + "id": "ws_0911b2daf2cc626d006a837099b31887d08d2d0269a6cd23de", + "type": "web_search_call", + "status": "completed", + "action": { + "type": "search", + "queries": [ + "Search the web for a recent AI news headline and answer in one sentence." + ], + "query": "Search the web for a recent AI news headline and answer in one sentence." + } + }, + { + "id": "msg_0911b2daf2cc626d006a83709ad25c87d0a02c9444c591bbb4", + "type": "message", + "status": "completed", + "content": [ + { + "type": "output_text", + "annotations": [ + { + "type": "url_citation", + "end_index": 332, + "start_index": 233, + "title": "New York Post launches \"Hamilton\" AI chatbot", + "url": "https://www.axios.com/2026/08/11/new-york-post-hamilton-ai-chatbot?utm_source=openai" + } + ], + "logprobs": [], + "text": "The New York Post Media Group has launched \"Hamilton,\" an AI chatbot designed to enhance personalized news experiences, marking the first collaboration between Google Cloud and a North American news outlet to support such a feature. ([axios.com](https://www.axios.com/2026/08/11/new-york-post-hamilton-ai-chatbot?utm_source=openai)) " + } + ], + "role": "assistant" + } + ], + "parallel_tool_calls": true, + "presence_penalty": 0.0, + "previous_response_id": null, + "prompt_cache_key": null, + "prompt_cache_retention": "in_memory", + "reasoning": { + "context": null, + "effort": null, + "summary": null + }, + "safety_identifier": null, + "service_tier": "auto", + "store": true, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + }, + "verbosity": "medium" + }, + "tool_choice": { + "type": "web_search_preview" + }, + "tool_usage": { + "image_gen": { + "input_tokens": 0, + "input_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "output_tokens": 0, + "output_tokens_details": { + "image_tokens": 0, + "text_tokens": 0 + }, + "total_tokens": 0 + }, + "web_search": { + "num_requests": 1 + } + }, + "tools": [ + { + "type": "web_search_preview", + "search_content_types": [ + "text" + ], + "search_context_size": "low", + "user_location": { + "type": "approximate", + "city": null, + "country": "US", + "region": null, + "timezone": null + } + } + ], + "top_logprobs": 0, + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 323, + "input_tokens_details": { + "cache_write_tokens": 0, + "cached_tokens": 0 + }, + "output_tokens": 73, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 396 + }, + "user": null, + "metadata": {} +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7c0db28da6b8.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7c0db28da6b8.json new file mode 100644 index 00000000..10c57661 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-7c0db28da6b8.json @@ -0,0 +1,49 @@ +{ + "id" : "a17f0a08-90cf-3039-a216-15f16dbeedf0", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"messages\":[{\"content\":\"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\",\"role\":\"user\"}],\"model\":\"o4-mini\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-7c0db28da6b8.json", + "headers" : { + "Server" : "cloudflare", + "x-ratelimit-reset-tokens" : "0s", + "set-cookie" : "__cf_bm=Wky4DpVpTg7LMCTKr3ZOE9ZrY3w_.7Zx_.tQJ_dwUkI-1786682651.1902754-1.0.1.1-_5dU7Ifp7l3K1Eg1dXMsO63q0vFVHk2LQSqJZkQrbml3gWFkrwA7TGO6kZkUNJJIOq1U0axg3NrBUO3YO3w_ZWDP0Uy.LNbn9TcHHkC0gbI8jpuhRxDt_rJ5aJQRQy2y; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 14 Aug 2026 05:14:18 GMT", + "Access-Control-Expose-Headers" : [ "CF-Ray", "X-Request-ID", "CF-Ray" ], + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json", + "x-request-id" : "req_3dc11519d9c8408abd0cb1c195d19d19", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "CF-Ray" : "a2ad4d89ee3f1f9a-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999970", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 14 Aug 2026 04:44:18 GMT", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "6333", + "alt-svc" : "h3=\":443\"; ma=86400" + } + }, + "uuid" : "a17f0a08-90cf-3039-a216-15f16dbeedf0", + "persistent" : true, + "insertionIndex" : 37 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-b5bcb38ce8df.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-b5bcb38ce8df.json new file mode 100644 index 00000000..84459ff3 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/chat_completions-b5bcb38ce8df.json @@ -0,0 +1,49 @@ +{ + "id" : "2c18cacf-35a1-374b-9f56-93255fa2c5ec", + "name" : "chat_completions", + "request" : { + "url" : "/chat/completions", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\n \"model\" : \"o4-mini\",\n \"messages\" : [ {\n \"role\" : \"user\",\n \"content\" : \"Look at this sequence: 2, 6, 12, 20, 30. What is the pattern and what would be the formula for the nth term?\\n\"\n } ],\n \"stream\" : false,\n \"reasoning_effort\" : \"medium\"\n}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "chat_completions-b5bcb38ce8df.json", + "headers" : { + "x-request-id" : "req_08731d3c4d734f9dbec8937ff43f6b9a", + "x-ratelimit-limit-tokens" : "150000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2ad4d87c8531421-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "2ms", + "x-ratelimit-remaining-tokens" : "149999970", + "x-openai-proxy-wasm" : "v0.1", + "x-ratelimit-remaining-requests" : "29999", + "Date" : "Fri, 14 Aug 2026 04:44:16 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=a2csL43EaTjPoUL5rKW8RS3vqRVUbFvwoLqGuFUXFj8-1786682650.844532-1.0.1.1-yVMQBonLnMHpTtOz6AqSFb2krLVftOtfFEkpd2nsZoy7sHK04YLiL3rt7Bp1epw8TNp0HKYG3M4IRs4EADUzn5YO01gAdjaqU2MMGRZX7WXZLBHjvCpfe4_OzAqJZ.Vh; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 14 Aug 2026 05:14:16 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "30000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "4468", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "2c18cacf-35a1-374b-9f56-93255fa2c5ec", + "persistent" : true, + "insertionIndex" : 38 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-0c37df81d2cf.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-0c37df81d2cf.json new file mode 100644 index 00000000..4cf01a06 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-0c37df81d2cf.json @@ -0,0 +1,48 @@ +{ + "id" : "281f816f-d189-3cf5-88f9-fd4fdc9fe66d", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"input\":[{\"content\":\"What is one recent headline about artificial intelligence? Use web search.\",\"role\":\"user\"}],\"model\":\"gpt-4o\",\"tools\":[{\"type\":\"web_search_preview\"}]}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-0c37df81d2cf.json", + "headers" : { + "x-request-id" : "req_caffbff4fcfa4447b1f0f244b349c260", + "x-ratelimit-limit-tokens" : "30000000", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2b477186f387696-SEA", + "X-Content-Type-Options" : "nosniff", + "x-ratelimit-reset-requests" : "6ms", + "x-ratelimit-remaining-tokens" : "29999667", + "x-ratelimit-remaining-requests" : "9999", + "Date" : "Sat, 15 Aug 2026 01:36:00 GMT", + "x-ratelimit-reset-tokens" : "0s", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=fKB9N5Strq0k5Ze6sBfi4jGPWxkXhYwFN8qyUXdPsIM-1786757753.6642315-1.0.1.1-wyZZ9kuB_61Xx6odgjT2OxPYQpP8j3xce70cTL5rB304kOCWgffE6Fj.BtDgeYCAyjCkI7SOuZ4KDifin5OZNXNfsEcE5OgeU7w.lKUK9iMdxo4FO.1aWZFgahNgnkqF; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Sat, 15 Aug 2026 02:06:00 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "x-ratelimit-limit-requests" : "10000", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "6801", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "281f816f-d189-3cf5-88f9-fd4fdc9fe66d", + "persistent" : true, + "insertionIndex" : 38 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-31d9a6eee624.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-31d9a6eee624.json new file mode 100644 index 00000000..5718205d --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-31d9a6eee624.json @@ -0,0 +1,42 @@ +{ + "id" : "4ecd239c-fa2c-3a94-9038-bb602a460baa", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"input\":[{\"content\":\"What is one recent headline about artificial intelligence? Use web search.\",\"role\":\"user\"}],\"model\":\"gpt-4o\",\"tools\":[{\"type\":\"web_search_preview\"}],\"stream\":true}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-31d9a6eee624.txt", + "headers" : { + "x-request-id" : "req_2f1291ac8203453fb7476805c8eb5c01", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2b476dad9132537-SEA", + "X-Content-Type-Options" : "nosniff", + "Date" : "Sat, 15 Aug 2026 01:35:47 GMT", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=q6tyEpyrXqDMtWwq2JvbVBjmkxbDf7l2v1_lo5R3X98-1786757743.8158913-1.0.1.1-WFRbjAqwnEOAf4BovtQaPxjU47q0SMcLUlfBkd949mJ48TGnhQ4BTdgbCvT.aHYoHKwo4FBtK.gTmhGWs3Mtaca2Iq2TajrUg.ffrJwJR9cPWwHXRpjj9IVj1f7eEZIA; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Sat, 15 Aug 2026 02:05:47 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "198", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "text/event-stream; charset=utf-8" + } + }, + "uuid" : "4ecd239c-fa2c-3a94-9038-bb602a460baa", + "persistent" : true, + "insertionIndex" : 39 +} \ No newline at end of file diff --git a/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-d93cfca55488.json b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-d93cfca55488.json new file mode 100644 index 00000000..1a772a82 --- /dev/null +++ b/test-harness/src/testFixtures/resources/cassettes/openai/mappings/responses-d93cfca55488.json @@ -0,0 +1,42 @@ +{ + "id" : "dde6da21-001f-3f62-a4e7-39705cf9371a", + "name" : "responses", + "request" : { + "url" : "/responses", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "equalToJson" : "{\"input\":[{\"role\":\"user\",\"content\":\"Search the web for a recent AI news headline and answer in one sentence.\"}],\"model\":\"gpt-4o\",\"tool_choice\":{\"type\":\"web_search_preview\"},\"tools\":[{\"type\":\"web_search_preview\",\"search_context_size\":\"low\"}]}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : false + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "responses-d93cfca55488.json", + "headers" : { + "x-request-id" : "req_a0817c80ef1844f4b77229d486899cb5", + "openai-organization" : "braintrust-data", + "Server" : "cloudflare", + "CF-Ray" : "a2cb775ae8a975ca-SEA", + "X-Content-Type-Options" : "nosniff", + "Date" : "Mon, 17 Aug 2026 20:35:39 GMT", + "access-control-expose-headers" : [ "X-Request-ID", "CF-Ray", "CF-Ray" ], + "set-cookie" : "__cf_bm=33AYcaWB.a8ohqFcwrCgFS2TtFNhkYwBy4_P18.Oces-1786998936.7849169-1.0.1.1-oEAPKODxPG.LP_o_x5lDnW7s2f8Amuhl75lBSXL0ZVyhijgnvbhfyO_.Lt9NCLeVmx8H25cjFRPLMWcz9yqaQwrIaDeHhANJqhjol3kRJxKCs62jj2A7UcwH2eeNZl.T; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Mon, 17 Aug 2026 21:05:39 GMT", + "Strict-Transport-Security" : "max-age=31536000; includeSubDomains; preload", + "CF-Cache-Status" : "DYNAMIC", + "openai-version" : "2020-10-01", + "openai-processing-ms" : "2398", + "alt-svc" : "h3=\":443\"; ma=86400", + "openai-project" : "proj_vsCSXafhhByzWOThMrJcZiw9", + "Content-Type" : "application/json" + } + }, + "uuid" : "dde6da21-001f-3f62-a4e7-39705cf9371a", + "persistent" : true, + "insertionIndex" : 40 +} \ No newline at end of file From 35fa0e25b3a5606876cda50cb2671e58880e3372 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Thu, 13 Aug 2026 22:49:35 -0600 Subject: [PATCH 2/4] bump spec version and tweak google attributes --- .../com/google/genai/BraintrustApiClient.java | 2 +- .../genai/v1_18_0/BraintrustGenAITest.java | 4 ++-- .../sdkspecimpl/SpecClientRegistry.java | 17 +++++++++++++++- .../sdkspecimpl/clients/GoogleSpecClient.java | 20 ++++++++++++++++++- gradle.properties | 2 +- 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/braintrust-sdk/instrumentation/genai_1_18_0/src/main/java/com/google/genai/BraintrustApiClient.java b/braintrust-sdk/instrumentation/genai_1_18_0/src/main/java/com/google/genai/BraintrustApiClient.java index dc52db2d..1a108cdb 100644 --- a/braintrust-sdk/instrumentation/genai_1_18_0/src/main/java/com/google/genai/BraintrustApiClient.java +++ b/braintrust-sdk/instrumentation/genai_1_18_0/src/main/java/com/google/genai/BraintrustApiClient.java @@ -55,7 +55,7 @@ private void tagSpan( @Nullable String responseBody) { try { Map metadata = new java.util.HashMap<>(); - metadata.put("provider", "gemini"); + metadata.put("provider", "google"); // Parse request if (requestBody != null) { diff --git a/braintrust-sdk/instrumentation/genai_1_18_0/src/test/java/dev/braintrust/instrumentation/genai/v1_18_0/BraintrustGenAITest.java b/braintrust-sdk/instrumentation/genai_1_18_0/src/test/java/dev/braintrust/instrumentation/genai/v1_18_0/BraintrustGenAITest.java index 35b2c683..bc2e066e 100644 --- a/braintrust-sdk/instrumentation/genai_1_18_0/src/test/java/dev/braintrust/instrumentation/genai/v1_18_0/BraintrustGenAITest.java +++ b/braintrust-sdk/instrumentation/genai_1_18_0/src/test/java/dev/braintrust/instrumentation/genai/v1_18_0/BraintrustGenAITest.java @@ -67,7 +67,7 @@ void testWrapGemini() { span.getAttributes().get(AttributeKey.stringKey("braintrust.metadata")); assertNotNull(metadataJson, "braintrust.metadata should be set"); var metadata = JSON_MAPPER.readTree(metadataJson); - assertEquals("gemini", metadata.get("provider").asText()); + assertEquals("google", metadata.get("provider").asText()); assertEquals(MODEL_ID, metadata.get("model").asText()); assertEquals(0.0, metadata.get("temperature").asDouble()); assertEquals(50, metadata.get("maxOutputTokens").asInt()); @@ -145,7 +145,7 @@ void testWrapGeminiAsync() { span.getAttributes().get(AttributeKey.stringKey("braintrust.metadata")); assertNotNull(metadataJson, "braintrust.metadata should be set"); var metadata = JSON_MAPPER.readTree(metadataJson); - assertEquals("gemini", metadata.get("provider").asText()); + assertEquals("google", metadata.get("provider").asText()); assertEquals(MODEL_ID, metadata.get("model").asText()); assertEquals(0.0, metadata.get("temperature").asDouble()); assertEquals(50, metadata.get("maxOutputTokens").asInt()); diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java index ad3987e9..e5eebeb1 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java @@ -32,7 +32,22 @@ public final class SpecClientRegistry { * nothing instead of producing a failing "unsupported" test. Prefer registering a client; reach * for this only when no Java client can express the spec at all. */ - private static final Set KNOWN_UNSUPPORTED_SPECS = Set.of(); + private static final Set KNOWN_UNSUPPORTED_SPECS = + Set.of( + // Brand-new Google features added in braintrust-spec v0.0.10. The genai + // instrumentation / GoogleSpecClient don't implement them yet (thinking, + // grounding tools, response modalities, per-modality prompt-token extraction), + // so they're marked unsupported rather than half-implemented. GoogleSpecClient + // also filters these out of supports(); see its UNSUPPORTED_SPECS. + "google/thinking", + "google/grounding", + "google/streaming", + "google/generated_audio_usage", + "google/generated_image_usage", + "google/attachments", + // Google Interactions API (/v1/interactions) — no Java client exists. + "google/interactions", + "google/interactions_streaming"); private static final List CLIENTS = Stream.of( diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/GoogleSpecClient.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/GoogleSpecClient.java index a2bb597e..8ff00341 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/GoogleSpecClient.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/GoogleSpecClient.java @@ -17,6 +17,23 @@ /** Google Gemini (google-genai) client: generateContent (sync + streaming). */ public final class GoogleSpecClient implements SpecClient { + /** + * {@code :generateContent} specs that require Google features not yet implemented in the genai + * instrumentation / this client (thinking, grounding tools, response modalities, and + * per-modality prompt-token extraction for attachments). They are additionally listed in {@link + * dev.braintrust.sdkspecimpl.SpecClientRegistry#KNOWN_UNSUPPORTED_SPECS} so they load to a + * deliberate skip instead of a failing "unsupported" sentinel. Remove an entry here once the + * corresponding feature lands. + */ + private static final java.util.Set UNSUPPORTED_SPECS = + java.util.Set.of( + "thinking", + "grounding", + "streaming", + "generated_audio_usage", + "generated_image_usage", + "attachments"); + private volatile Client geminiClient; @Override @@ -31,7 +48,8 @@ public String provider() { @Override public boolean supports(LlmSpanSpec spec) { - return spec.endpoint().contains(":generateContent"); + return spec.endpoint().contains(":generateContent") + && !UNSUPPORTED_SPECS.contains(spec.name()); } @Override diff --git a/gradle.properties b/gradle.properties index 103fc769..52a608c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.daemon=true org.gradle.warning.mode=summary # braintrust-spec git ref (SHA or tag) used by btx tests -braintrustSpecRef=v0.0.9 +braintrustSpecRef=v0.0.10 # braintrust-openapi commit SHA used by braintrust-api braintrustOpenApiRef=64b79cb9122f50a74eac98ea86c3ec1858c0cdd1 From 16d0d97fb24894f6d73f046fa5389629b78c5f53 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Mon, 17 Aug 2026 16:18:28 -0600 Subject: [PATCH 3/4] btx: support nested spans in spec yaml assertion --- .../sdkspecimpl/LlmSpanSpecTest.java | 25 +++++++++-- .../braintrust/sdkspecimpl/SpanConverter.java | 8 ++++ .../braintrust/sdkspecimpl/SpanFetcher.java | 45 ++++++++++++++++++- .../braintrust/sdkspecimpl/SpanValidator.java | 3 ++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/LlmSpanSpecTest.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/LlmSpanSpecTest.java index bd56b1f7..9f939cd9 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/LlmSpanSpecTest.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/LlmSpanSpecTest.java @@ -90,8 +90,9 @@ static Stream specs() throws Exception { SpecClientRegistry.execute( spec, CTX); totalExpectedSpans.addAndGet( - spec.expectedBrainstoreSpans() - .size() + countExpectedSpans( + spec + .expectedBrainstoreSpans()) + 1); return Arguments.of( spec, rootSpanId); @@ -111,9 +112,27 @@ void runSpec(LlmSpanSpec spec, String rootSpanId) throws Exception { if (SpecClientRegistry.UNSUPPORTED_CLIENT_ID.equals(spec.client())) { org.junit.jupiter.api.Assertions.fail(SpecClientRegistry.unsupportedSpecMessage(spec)); } - int expectedSpanCount = spec.expectedBrainstoreSpans().size(); + int expectedSpanCount = countExpectedSpans(spec.expectedBrainstoreSpans()); List> brainstoreSpans = SPAN_FETCHER.fetch(rootSpanId, expectedSpanCount); SpanValidator.validate(brainstoreSpans, spec.expectedBrainstoreSpans(), spec.displayName()); } + + /** + * Total number of spans a spec expects, counting nested {@code child_spans} recursively. Used + * to size the {@code awaitExportedSpans} gate so it doesn't return before child (e.g. tool) + * spans have flushed. + */ + @SuppressWarnings("unchecked") + private static int countExpectedSpans(List> spans) { + int total = 0; + for (Map span : spans) { + total += 1; + Object children = span.get("child_spans"); + if (children instanceof List l) { + total += countExpectedSpans((List>) l); + } + } + return total; + } } diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanConverter.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanConverter.java index 49ee945e..890e37c0 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanConverter.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanConverter.java @@ -57,6 +57,14 @@ private static boolean isLlmInstrumentationSpan(SpanData span) { private static Map toSingleBrainstoreSpan(SpanData span) { Map result = new LinkedHashMap<>(); + // span_id / span_parents mirror the brainstore fields so the fetch layer can rebuild the + // parent→child tree in REPLAY mode (live BTQL spans already carry these). + result.put("span_id", span.getSpanContext().getSpanId()); + var parentContext = span.getParentSpanContext(); + result.put( + "span_parents", + parentContext.isValid() ? List.of(parentContext.getSpanId()) : null); + result.put("name", span.getName()); result.put("metrics", parseJsonMap(span, "braintrust.metrics")); result.put("metadata", parseJsonMap(span, "braintrust.metadata")); diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanFetcher.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanFetcher.java index fbd69d9c..29b47aa1 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanFetcher.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanFetcher.java @@ -41,7 +41,7 @@ public List> fetch(String rootSpanId, int numExpectedChildSp if (isReplayMode()) { // Fast path: convert the in-memory OTel spans to brainstore format locally. - return convertedOtelSpans; + return buildSpanTree(convertedOtelSpans); } // Live path: spans were actually sent to Braintrust — fetch them back via BTQL. @@ -86,7 +86,45 @@ public List> fetch(String rootSpanId, int numExpectedChildSp // Cross-check that our local OTel→brainstore conversion matches the real thing. assertConverterMatchesBrainstore(convertedOtelSpans, brainstoreSpans, rootSpanId); - return brainstoreSpans; + return buildSpanTree(brainstoreSpans); + } + + /** + * Reshape a flat list of brainstore spans into a forest: each span gets a nested {@code + * child_spans} list, and only top-level spans (those whose parent is not among the fetched + * spans — i.e. children of the already-excluded root wrapper) are returned. Sibling order is + * preserved from the input list. This lets specs assert nested tool spans (e.g. a {@code + * web_search_call} child of an LLM span) via a recursive {@code child_spans:} structure. + */ + @SuppressWarnings("unchecked") + static List> buildSpanTree(List> flatSpans) { + // Work on mutable copies so we can attach child_spans without mutating the inputs. + List> spans = new java.util.ArrayList<>(); + Map> byId = new java.util.LinkedHashMap<>(); + for (Map span : flatSpans) { + Map copy = new java.util.LinkedHashMap<>(span); + copy.put("child_spans", new java.util.ArrayList>()); + spans.add(copy); + Object id = span.get("span_id"); + if (id instanceof String s) { + byId.put(s, copy); + } + } + + List> topLevel = new java.util.ArrayList<>(); + for (Map span : spans) { + Map parent = null; + Object parents = span.get("span_parents"); + if (parents instanceof List l && !l.isEmpty() && l.get(0) instanceof String pid) { + parent = byId.get(pid); + } + if (parent != null) { + ((List>) parent.get("child_spans")).add(span); + } else { + topLevel.add(span); + } + } + return topLevel; } /** @@ -131,6 +169,9 @@ private static void assertConverterMatchesBrainstore( Map convWithoutName = new java.util.LinkedHashMap<>(conv); convWithoutName.remove("name"); convWithoutName.remove("metrics"); + // span_id / span_parents are structural (used only for tree-building), not content. + convWithoutName.remove("span_id"); + convWithoutName.remove("span_parents"); assertIsSubset(convWithoutName, realSpan, ctx); assertMetricsKeysPresent(conv, realSpan, ctx); } diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java index 4029fbdf..387ed177 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java @@ -18,6 +18,9 @@ *
  • {@code span_attributes} — type, name *
  • {@code input} — input messages *
  • {@code output} — output choices / content + *
  • {@code child_spans} — a nested list of child-span assertions, validated recursively against + * the span's actual children (built by {@link SpanFetcher#buildSpanTree}). Handled by the + * generic recursion below; a span that omits it asserts nothing about its children. * * *

    Spans arrive here already in brainstore format, produced either by {@link SpanConverter} From 1591cad79dea0bdc78d17a834dd9c67183687765 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Tue, 18 Aug 2026 12:29:09 -0600 Subject: [PATCH 4/4] support web search in openai-responses + anthropic --- .../anthropic/v2_2_0/TracingHttpClient.java | 26 +- ...raintrustAnthropicServerSideSpansTest.java | 136 +++++ .../v2_30_0/BraintrustBedrockInterceptor.java | 12 +- .../langchain/v1_8_0/WrappedHttpClient.java | 15 +- .../openai/v2_15_0/TracingHttpClient.java | 28 +- .../BraintrustOpenAIServerSideSpansTest.java | 144 +++++ .../openai/v2_15_0/BraintrustOpenAITest.java | 2 + .../springai/v1_0_0/BraintrustSpringAI.java | 8 +- .../InstrumentationSemConv.java | 545 +++++++++++++++++- .../InstrumentationSemConvTest.java | 351 +++++++++++ .../braintrust/sdkspecimpl/SpanValidator.java | 55 +- .../sdkspecimpl/SpecClientRegistry.java | 15 +- .../clients/SpringAi1AnthropicSpecClient.java | 13 +- gradle.properties | 2 +- 14 files changed, 1301 insertions(+), 51 deletions(-) create mode 100644 braintrust-sdk/instrumentation/anthropic_2_2_0/src/test/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropicServerSideSpansTest.java create mode 100644 braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAIServerSideSpansTest.java create mode 100644 braintrust-sdk/src/test/java/dev/braintrust/instrumentation/InstrumentationSemConvTest.java diff --git a/braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/TracingHttpClient.java b/braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/TracingHttpClient.java index 207196b5..7fc9f905 100644 --- a/braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/TracingHttpClient.java +++ b/braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/TracingHttpClient.java @@ -128,7 +128,7 @@ public void close() { inputJson); var response = underlying.execute(bufferedRequest, requestOptions); - return new TeeingStreamHttpResponse(response, span); + return new TeeingStreamHttpResponse(response, span, tracer); } catch (Exception e) { InstrumentationSemConv.tagLLMSpanResponse(span, e); span.end(); @@ -157,7 +157,9 @@ public void close() { return underlying .executeAsync(bufferedRequest, requestOptions) .thenApply( - response -> (HttpResponse) new TeeingStreamHttpResponse(response, span)) + response -> + (HttpResponse) + new TeeingStreamHttpResponse(response, span, tracer)) .whenComplete( (response, t) -> { if (t != null) { @@ -237,14 +239,16 @@ private static String readBodyAsString(HttpRequestBody body) { private static final class TeeingStreamHttpResponse implements HttpResponse { private final HttpResponse delegate; private final Span span; + private final Tracer tracer; private final long spanStartNanos = System.nanoTime(); private final AtomicLong timeToFirstTokenNanos = new AtomicLong(); private final ByteArrayOutputStream teeBuffer = new ByteArrayOutputStream(); private final InputStream teeStream; - TeeingStreamHttpResponse(HttpResponse delegate, Span span) { + TeeingStreamHttpResponse(HttpResponse delegate, Span span, Tracer tracer) { this.delegate = delegate; this.span = span; + this.tracer = tracer; this.teeStream = new TeeInputStream( delegate.body(), teeBuffer, this::onFirstByte, this::onStreamClosed); @@ -260,7 +264,9 @@ private void onStreamClosed() { synchronized (teeBuffer) { bytes = teeBuffer.toByteArray(); } - tagSpanFromBuffer(span, bytes, timeToFirstTokenNanos.get()); + // tagLLMSpanResponse also emits child spans for any server-side tool calls (web + // search, etc.) nested under the LLM span while it is still live. + tagSpanFromBuffer(tracer, span, bytes, timeToFirstTokenNanos.get()); } finally { span.end(); } @@ -354,7 +360,8 @@ private void notifyClosed() { // Span tagging from buffered bytes // ------------------------------------------------------------------------- - private static void tagSpanFromBuffer(Span span, byte[] bytes, Long timeToFirstTokenNanos) { + private static void tagSpanFromBuffer( + Tracer tracer, Span span, byte[] bytes, Long timeToFirstTokenNanos) { if (bytes.length == 0) return; try { String firstLine = firstNonEmptyLine(bytes); @@ -364,13 +371,15 @@ private static void tagSpanFromBuffer(Span span, byte[] bytes, Long timeToFirstT firstLine != null && (firstLine.startsWith("data:") || firstLine.startsWith("event:")); if (isSse) { - tagSpanFromSseBytes(span, bytes, timeToFirstTokenNanos); + tagSpanFromSseBytes(tracer, span, bytes, timeToFirstTokenNanos); } else { // Non-streaming: plain Message JSON — pass it whole, no time_to_first_token + String responseJson = new String(bytes, StandardCharsets.UTF_8); InstrumentationSemConv.tagLLMSpanResponse( + tracer, span, InstrumentationSemConv.PROVIDER_NAME_ANTHROPIC, - new String(bytes, StandardCharsets.UTF_8), + responseJson, null); } } catch (Exception e) { @@ -406,7 +415,7 @@ private static String firstNonEmptyLine(byte[] bytes) { * assembled {@link com.anthropic.models.messages.Message} for the span. */ private static void tagSpanFromSseBytes( - Span span, byte[] sseBytes, Long timeToFirstTokenNanos) { + Tracer tracer, Span span, byte[] sseBytes, Long timeToFirstTokenNanos) { try { var mapper = BraintrustJsonMapper.get(); var reader = @@ -427,6 +436,7 @@ private static void tagSpanFromSseBytes( } String assembledMessageJson = BraintrustJsonMapper.toJson(accumulator.message()); InstrumentationSemConv.tagLLMSpanResponse( + tracer, span, InstrumentationSemConv.PROVIDER_NAME_ANTHROPIC, assembledMessageJson, diff --git a/braintrust-sdk/instrumentation/anthropic_2_2_0/src/test/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropicServerSideSpansTest.java b/braintrust-sdk/instrumentation/anthropic_2_2_0/src/test/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropicServerSideSpansTest.java new file mode 100644 index 00000000..f8540b03 --- /dev/null +++ b/braintrust-sdk/instrumentation/anthropic_2_2_0/src/test/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropicServerSideSpansTest.java @@ -0,0 +1,136 @@ +package dev.braintrust.instrumentation.anthropic.v2_2_0; + +import static org.junit.jupiter.api.Assertions.*; + +import com.anthropic.client.AnthropicClient; +import com.anthropic.client.okhttp.AnthropicOkHttpClient; +import com.anthropic.models.messages.MessageCreateParams; +import com.anthropic.models.messages.Model; +import com.anthropic.models.messages.WebSearchTool20250305; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.braintrust.TestHarness; +import dev.braintrust.instrumentation.Instrumenter; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.List; +import lombok.SneakyThrows; +import net.bytebuddy.agent.ByteBuddyAgent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Verifies that Anthropic server-side tool calls are captured as both a cost metric on the LLM span + * and a child {@code type:"tool"} span parented to it, giving each call its own cost/latency + * visibility on the trace timeline. Web search ({@code server_tool_use_web_search_requests}) is the + * case exercised here. + */ +public class BraintrustAnthropicServerSideSpansTest { + private static final String TEST_MODEL = "claude-sonnet-4-5-20250929"; + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); + private static final AttributeKey SPAN_ATTRIBUTES = + AttributeKey.stringKey("braintrust.span_attributes"); + private static final AttributeKey METADATA = + AttributeKey.stringKey("braintrust.metadata"); + private static final AttributeKey METRICS = + AttributeKey.stringKey("braintrust.metrics"); + + @BeforeAll + public static void beforeAll() { + var instrumentation = ByteBuddyAgent.install(); + Instrumenter.install( + instrumentation, BraintrustAnthropicServerSideSpansTest.class.getClassLoader()); + } + + private TestHarness testHarness; + + @BeforeEach + void beforeEach() { + testHarness = TestHarness.setup(); + } + + private static MessageCreateParams webSearchRequest() { + return MessageCreateParams.builder() + .model(Model.of(TEST_MODEL)) + .maxTokens(1024) + .addUserMessage( + "Search the web for one recent AI news headline and answer in one" + + " sentence.") + .addTool(WebSearchTool20250305.builder().maxUses(3).build()) + .build(); + } + + @Test + @SneakyThrows + void testWebSearch() { + AnthropicClient client = + AnthropicOkHttpClient.builder() + .baseUrl(testHarness.anthropicBaseUrl()) + .apiKey(testHarness.anthropicApiKey()) + .build(); + + var response = client.messages().create(webSearchRequest()); + assertNotNull(response); + + assertWebSearch(testHarness.awaitExportedSpans(2)); + } + + @Test + @SneakyThrows + void testWebSearchStreaming() { + AnthropicClient client = + AnthropicOkHttpClient.builder() + .baseUrl(testHarness.anthropicBaseUrl()) + .apiKey(testHarness.anthropicApiKey()) + .build(); + + try (var stream = client.messages().createStreaming(webSearchRequest())) { + stream.stream().forEach(event -> {}); + } + + assertWebSearch(testHarness.awaitExportedSpans(2)); + } + + @SneakyThrows + private static void assertWebSearch(List spans) { + var llmSpans = spans.stream().filter(s -> isType(s, "llm")).toList(); + assertEquals(1, llmSpans.size(), "expected a single LLM span"); + var llm = llmSpans.get(0); + + // Cost metric on the LLM span. + JsonNode metrics = JSON_MAPPER.readTree(llm.getAttributes().get(METRICS)); + assertTrue( + metrics.has("server_tool_use_web_search_requests"), + "expected server_tool_use_web_search_requests metric, got: " + metrics); + assertTrue(metrics.get("server_tool_use_web_search_requests").asDouble() >= 1.0); + + // At least one web_search tool span, parented to the LLM span. + var toolSpans = + spans.stream() + .filter(s -> isType(s, "tool")) + .filter(s -> "web_search".equals(s.getName())) + .toList(); + assertFalse( + toolSpans.isEmpty(), + "expected at least one web_search tool span, got: " + + spans.stream().map(SpanData::getName).toList()); + + for (var tool : toolSpans) { + assertEquals( + llm.getSpanId(), + tool.getParentSpanId(), + "web_search tool span must be a child of the LLM span"); + JsonNode metadata = JSON_MAPPER.readTree(tool.getAttributes().get(METADATA)); + assertEquals("server_tool_use", metadata.path("tool_call_type").asText()); + assertEquals("web_search_tool_result", metadata.path("tool_result_type").asText()); + assertFalse(metadata.path("tool_use_id").asText().isEmpty()); + } + } + + @SneakyThrows + private static boolean isType(SpanData span, String type) { + String attr = span.getAttributes().get(SPAN_ATTRIBUTES); + return attr != null && type.equals(JSON_MAPPER.readTree(attr).path("type").asText()); + } +} diff --git a/braintrust-sdk/instrumentation/aws_bedrock_2_30_0/src/main/java/dev/braintrust/instrumentation/awsbedrock/v2_30_0/BraintrustBedrockInterceptor.java b/braintrust-sdk/instrumentation/aws_bedrock_2_30_0/src/main/java/dev/braintrust/instrumentation/awsbedrock/v2_30_0/BraintrustBedrockInterceptor.java index 6f3e0d58..ab0cb63d 100644 --- a/braintrust-sdk/instrumentation/aws_bedrock_2_30_0/src/main/java/dev/braintrust/instrumentation/awsbedrock/v2_30_0/BraintrustBedrockInterceptor.java +++ b/braintrust-sdk/instrumentation/aws_bedrock_2_30_0/src/main/java/dev/braintrust/instrumentation/awsbedrock/v2_30_0/BraintrustBedrockInterceptor.java @@ -146,7 +146,10 @@ public Optional modifyHttpResponseContent( try { String responseBodyStr = new String(bytes, StandardCharsets.UTF_8); InstrumentationSemConv.tagLLMSpanResponse( - span, InstrumentationSemConv.PROVIDER_NAME_BEDROCK, responseBodyStr); + tracer, + span, + InstrumentationSemConv.PROVIDER_NAME_BEDROCK, + responseBodyStr); } catch (Exception e) { log.debug("Failed to capture response body", e); } @@ -182,7 +185,7 @@ public Optional> modifyAsyncHttpResponseContent( Publisher original = publisherOpt.get(); Publisher teed = - subscriber -> original.subscribe(new TeeingSubscriber(subscriber, span)); + subscriber -> original.subscribe(new TeeingSubscriber(subscriber, span, tracer)); return Optional.of(teed); } @@ -238,6 +241,7 @@ private static String extractModelIdFromPath(String path) { private static class TeeingSubscriber implements Subscriber { private final Subscriber downstream; private final Span span; + private final Tracer tracer; private final MessageDecoder decoder = new MessageDecoder(); // Accumulated incrementally in onNext — no message list retained. @@ -248,9 +252,10 @@ private static class TeeingSubscriber implements Subscriber { private long startNanos; private Long timeToFirstTokenNanos = null; - TeeingSubscriber(Subscriber downstream, Span span) { + TeeingSubscriber(Subscriber downstream, Span span, Tracer tracer) { this.downstream = downstream; this.span = span; + this.tracer = tracer; } @Override @@ -304,6 +309,7 @@ public void onError(Throwable t) { public void onComplete() { try { InstrumentationSemConv.tagLLMSpanResponse( + tracer, span, InstrumentationSemConv.PROVIDER_NAME_BEDROCK, buildConverseJson(text.toString(), stopReason, inputTokens, outputTokens), diff --git a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/WrappedHttpClient.java b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/WrappedHttpClient.java index cfeeec89..c007649e 100644 --- a/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/WrappedHttpClient.java +++ b/braintrust-sdk/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/WrappedHttpClient.java @@ -49,7 +49,7 @@ public SuccessfulHttpResponse execute(HttpRequest request) tagRequest(span, request); var response = underlying.execute(request); InstrumentationSemConv.tagLLMSpanResponse( - span, options.providerName(), response.body()); + tracer, span, options.providerName(), response.body()); return response; } catch (Throwable t) { InstrumentationSemConv.tagLLMSpanResponse(span, t); @@ -73,7 +73,8 @@ public void execute(HttpRequest request, ServerSentEventListener listener) { tagRequest(span, request); underlying.execute( request, - new WrappedServerSentEventListener(listener, span, options.providerName())); + new WrappedServerSentEventListener( + listener, span, options.providerName(), tracer)); } catch (Throwable t) { InstrumentationSemConv.tagLLMSpanResponse(span, t); span.end(); @@ -97,7 +98,8 @@ public void execute( underlying.execute( request, parser, - new WrappedServerSentEventListener(listener, span, options.providerName())); + new WrappedServerSentEventListener( + listener, span, options.providerName(), tracer)); } catch (Throwable t) { InstrumentationSemConv.tagLLMSpanResponse(span, t); span.end(); @@ -122,16 +124,18 @@ static class WrappedServerSentEventListener implements ServerSentEventListener { private final ServerSentEventListener delegate; private final Span span; private final String providerName; + private final Tracer tracer; private final long startNanos = System.nanoTime(); private final AtomicLong timeToFirstTokenNanos = new AtomicLong(); private final SseResponseAccumulator accumulator = new SseResponseAccumulator(BraintrustJsonMapper.get()); WrappedServerSentEventListener( - ServerSentEventListener delegate, Span span, String providerName) { + ServerSentEventListener delegate, Span span, String providerName, Tracer tracer) { this.delegate = delegate; this.span = span; this.providerName = providerName; + this.tracer = tracer; } @Override @@ -188,8 +192,9 @@ private void accumulateChunk(String data) { private void finalizeSpan() { try { Long ttft = timeToFirstTokenNanos.get(); + String responseBody = accumulator.build(); InstrumentationSemConv.tagLLMSpanResponse( - span, providerName, accumulator.build(), ttft); + tracer, span, providerName, responseBody, ttft); } catch (Exception e) { log.debug("Failed to finalize streaming span", e); } diff --git a/braintrust-sdk/instrumentation/openai_2_15_0/src/main/java/dev/braintrust/instrumentation/openai/v2_15_0/TracingHttpClient.java b/braintrust-sdk/instrumentation/openai_2_15_0/src/main/java/dev/braintrust/instrumentation/openai/v2_15_0/TracingHttpClient.java index 6f9fac29..2d4d958b 100644 --- a/braintrust-sdk/instrumentation/openai_2_15_0/src/main/java/dev/braintrust/instrumentation/openai/v2_15_0/TracingHttpClient.java +++ b/braintrust-sdk/instrumentation/openai_2_15_0/src/main/java/dev/braintrust/instrumentation/openai/v2_15_0/TracingHttpClient.java @@ -130,7 +130,7 @@ public void close() { var response = underlying.execute(bufferedRequest, requestOptions); // Always tee the response body. onStreamClosed() detects whether the collected // bytes are SSE or plain JSON and tags the span accordingly. - return new TeeingStreamHttpResponse(response, span); + return new TeeingStreamHttpResponse(response, span, tracer); } catch (Exception e) { InstrumentationSemConv.tagLLMSpanResponse(span, e); span.end(); @@ -159,7 +159,9 @@ public void close() { return underlying .executeAsync(bufferedRequest, requestOptions) .thenApply( - response -> (HttpResponse) new TeeingStreamHttpResponse(response, span)) + response -> + (HttpResponse) + new TeeingStreamHttpResponse(response, span, tracer)) .whenComplete( (response, t) -> { if (t != null) { @@ -239,18 +241,18 @@ private static String readBodyAsString(HttpRequestBody body) { * the bytes are an SSE stream (first non-empty line starts with {@code "data: "}) or a plain * JSON response, and parses accordingly. */ - private static void tagSpanFromBuffer(Span span, byte[] bytes, Long timeToFirstTokenNanos) { + private static void tagSpanFromBuffer( + Tracer tracer, Span span, byte[] bytes, Long timeToFirstTokenNanos) { if (bytes.length == 0) return; try { String firstLine = firstNonEmptyLine(bytes); if (firstLine != null && (firstLine.startsWith("data:") || firstLine.startsWith("event:"))) { - tagSpanFromSseBytes(span, bytes, timeToFirstTokenNanos); + tagSpanFromSseBytes(tracer, span, bytes, timeToFirstTokenNanos); } else { + String responseJson = new String(bytes, StandardCharsets.UTF_8); InstrumentationSemConv.tagLLMSpanResponse( - span, - InstrumentationSemConv.PROVIDER_NAME_OPENAI, - new String(bytes, StandardCharsets.UTF_8)); + tracer, span, InstrumentationSemConv.PROVIDER_NAME_OPENAI, responseJson); } } catch (Exception e) { log.error("Could not tag span from response buffer", e); @@ -274,7 +276,7 @@ private static String firstNonEmptyLine(byte[] bytes) { * ChatCompletionAccumulator}, then tags the span with the reassembled output JSON. */ private static void tagSpanFromSseBytes( - Span span, byte[] sseBytes, Long timeToFirstTokenNanos) { + Tracer tracer, Span span, byte[] sseBytes, Long timeToFirstTokenNanos) { try { var reader = new BufferedReader( @@ -325,6 +327,7 @@ private static void tagSpanFromSseBytes( } if (null != responseJson) { InstrumentationSemConv.tagLLMSpanResponse( + tracer, span, InstrumentationSemConv.PROVIDER_NAME_OPENAI, responseJson, @@ -343,14 +346,16 @@ private static void tagSpanFromSseBytes( private static final class TeeingStreamHttpResponse implements HttpResponse { private final HttpResponse delegate; private final Span span; + private final Tracer tracer; private final long spanStartNanos = System.nanoTime(); private final AtomicLong timeToFirstTokenNanos = new AtomicLong(); private final ByteArrayOutputStream teeBuffer = new ByteArrayOutputStream(); private final InputStream teeStream; - TeeingStreamHttpResponse(HttpResponse delegate, Span span) { + TeeingStreamHttpResponse(HttpResponse delegate, Span span, Tracer tracer) { this.delegate = delegate; this.span = span; + this.tracer = tracer; this.teeStream = new TeeInputStream( delegate.body(), teeBuffer, this::onFirstByte, this::onStreamClosed); @@ -369,7 +374,10 @@ private void onStreamClosed() { synchronized (teeBuffer) { bytes = teeBuffer.toByteArray(); } - tagSpanFromBuffer(span, bytes, timeToFirstTokenNanos.get()); + // tagLLMSpanResponse also emits child spans for any server-side tool calls (web + // search, etc.) nested under the LLM span while it is still live. No-op for Chat + // Completions responses (no `output` array). + tagSpanFromBuffer(tracer, span, bytes, timeToFirstTokenNanos.get()); } finally { span.end(); } diff --git a/braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAIServerSideSpansTest.java b/braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAIServerSideSpansTest.java new file mode 100644 index 00000000..ceb6e443 --- /dev/null +++ b/braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAIServerSideSpansTest.java @@ -0,0 +1,144 @@ +package dev.braintrust.instrumentation.openai.v2_15_0; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.openai.client.OpenAIClient; +import com.openai.client.okhttp.OpenAIOkHttpClient; +import com.openai.core.http.StreamResponse; +import com.openai.helpers.ResponseAccumulator; +import com.openai.models.ChatModel; +import com.openai.models.responses.EasyInputMessage; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ResponseInputItem; +import com.openai.models.responses.ResponseStreamEvent; +import com.openai.models.responses.WebSearchTool; +import dev.braintrust.TestHarness; +import dev.braintrust.instrumentation.Instrumenter; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.List; +import lombok.SneakyThrows; +import net.bytebuddy.agent.ByteBuddyAgent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Verifies that server-side tool calls in the OpenAI Responses API are captured as child {@code + * type:"tool"} spans parented to the LLM span, giving each call its own cost/latency visibility on + * the trace timeline. Web search ({@code web_search_call}) is the case exercised here. + */ +public class BraintrustOpenAIServerSideSpansTest { + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); + private static final AttributeKey SPAN_ATTRIBUTES = + AttributeKey.stringKey("braintrust.span_attributes"); + private static final AttributeKey METADATA = + AttributeKey.stringKey("braintrust.metadata"); + + @BeforeAll + public static void beforeAll() { + var instrumentation = ByteBuddyAgent.install(); + Instrumenter.install( + instrumentation, BraintrustOpenAIServerSideSpansTest.class.getClassLoader()); + } + + private TestHarness testHarness; + + @BeforeEach + void beforeEach() { + testHarness = TestHarness.setup(); + } + + private static ResponseCreateParams webSearchRequest() { + return ResponseCreateParams.builder() + .model(ChatModel.GPT_4O) + .inputOfResponse( + List.of( + ResponseInputItem.ofEasyInputMessage( + EasyInputMessage.builder() + .role(EasyInputMessage.Role.USER) + .content( + "What is one recent headline about" + + " artificial intelligence? Use web" + + " search.") + .build()))) + .addTool( + WebSearchTool.builder().type(WebSearchTool.Type.WEB_SEARCH_PREVIEW).build()) + .build(); + } + + @Test + @SneakyThrows + void testResponsesWebSearch() { + OpenAIClient client = + OpenAIOkHttpClient.builder() + .baseUrl(testHarness.openAiBaseUrl()) + .apiKey(testHarness.openAiApiKey()) + .build(); + + Response response = client.responses().create(webSearchRequest()); + assertNotNull(response); + + var spans = testHarness.awaitExportedSpans(2); + assertWebSearchToolSpans(spans); + } + + @Test + @SneakyThrows + void testResponsesWebSearchStreaming() { + OpenAIClient client = + OpenAIOkHttpClient.builder() + .baseUrl(testHarness.openAiBaseUrl()) + .apiKey(testHarness.openAiApiKey()) + .build(); + + var accumulator = ResponseAccumulator.create(); + try (StreamResponse stream = + client.responses().createStreaming(webSearchRequest())) { + stream.stream().forEach(accumulator::accumulate); + } + assertFalse(accumulator.response().output().isEmpty(), "should generate a response"); + + var spans = testHarness.awaitExportedSpans(2); + assertWebSearchToolSpans(spans); + } + + @SneakyThrows + private static void assertWebSearchToolSpans(List spans) { + // Exactly one LLM span (the Responses request), plus one or more tool spans. + var llmSpans = spans.stream().filter(s -> isType(s, "llm")).toList(); + assertEquals(1, llmSpans.size(), "expected a single LLM span"); + var llm = llmSpans.get(0); + + var webSearchSpans = + spans.stream() + .filter(s -> isType(s, "tool")) + .filter(s -> "web_search_call".equals(s.getName())) + .toList(); + assertFalse( + webSearchSpans.isEmpty(), + "expected at least one web_search_call tool span, got spans: " + + spans.stream().map(SpanData::getName).toList()); + + for (var ws : webSearchSpans) { + assertEquals( + llm.getSpanId(), + ws.getParentSpanId(), + "web_search_call tool span must be a child of the LLM span"); + JsonNode metadata = JSON_MAPPER.readTree(ws.getAttributes().get(METADATA)); + assertEquals("web_search_call", metadata.path("tool_type").asText()); + } + } + + @SneakyThrows + private static boolean isType(SpanData span, String type) { + String attr = span.getAttributes().get(SPAN_ATTRIBUTES); + if (attr == null) { + return false; + } + return type.equals(JSON_MAPPER.readTree(attr).path("type").asText()); + } +} diff --git a/braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAITest.java b/braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAITest.java index b34e5fc7..218a4175 100644 --- a/braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAITest.java +++ b/braintrust-sdk/instrumentation/openai_2_15_0/src/test/java/dev/braintrust/instrumentation/openai/v2_15_0/BraintrustOpenAITest.java @@ -545,6 +545,8 @@ void testResponsesStreamingWithTools() { .toList(); assertFalse(functionCalls.isEmpty(), "model should call a function tool"); + // function_call is a client-side tool call — it stays in the LLM span output and does NOT + // produce a child span (only server-side tool calls do). So we expect exactly one span. var spans = testHarness.awaitExportedSpans(); assertEquals(1, spans.size()); var span = spans.get(0); diff --git a/braintrust-sdk/instrumentation/springai_1_0_0/src/main/java/dev/braintrust/instrumentation/springai/v1_0_0/BraintrustSpringAI.java b/braintrust-sdk/instrumentation/springai_1_0_0/src/main/java/dev/braintrust/instrumentation/springai/v1_0_0/BraintrustSpringAI.java index 7e9a7833..a30ebfac 100644 --- a/braintrust-sdk/instrumentation/springai_1_0_0/src/main/java/dev/braintrust/instrumentation/springai/v1_0_0/BraintrustSpringAI.java +++ b/braintrust-sdk/instrumentation/springai_1_0_0/src/main/java/dev/braintrust/instrumentation/springai/v1_0_0/BraintrustSpringAI.java @@ -198,7 +198,7 @@ public ClientHttpResponse intercept( byte[] responseBytes = response.getBody().readAllBytes(); String responseBody = new String(responseBytes, StandardCharsets.UTF_8); - InstrumentationSemConv.tagLLMSpanResponse(span, providerName, responseBody); + InstrumentationSemConv.tagLLMSpanResponse(tracer, span, providerName, responseBody); span.end(); return new BufferedClientHttpResponse(response, responseBytes); @@ -358,7 +358,11 @@ private Flux wrapStreamingBody( assembled.toString(), span, streamCtx); Long ttft = ttftNanos[0] >= 0 ? ttftNanos[0] : null; InstrumentationSemConv.tagLLMSpanResponse( - span, streamCtx.providerName(), responseBody, ttft); + tracer, + span, + streamCtx.providerName(), + responseBody, + ttft); } catch (Exception e) { log.debug("failed to tag streaming response", e); } diff --git a/braintrust-sdk/src/main/java/dev/braintrust/instrumentation/InstrumentationSemConv.java b/braintrust-sdk/src/main/java/dev/braintrust/instrumentation/InstrumentationSemConv.java index 1dc25cfe..43452486 100644 --- a/braintrust-sdk/src/main/java/dev/braintrust/instrumentation/InstrumentationSemConv.java +++ b/braintrust-sdk/src/main/java/dev/braintrust/instrumentation/InstrumentationSemConv.java @@ -4,12 +4,18 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import dev.braintrust.json.BraintrustJsonMapper; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import java.time.Instant; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.SneakyThrows; @@ -77,25 +83,38 @@ public static void tagLLMSpanRequest( } public static void tagLLMSpanResponse( - Span span, @Nonnull String providerName, @Nonnull String responseBody) { - tagLLMSpanResponse(span, providerName, responseBody, null); + @Nonnull Tracer tracer, + Span span, + @Nonnull String providerName, + @Nonnull String responseBody) { + tagLLMSpanResponse(tracer, span, providerName, responseBody, null); } + /** + * Tag a span with the LLM response and emit child spans for any server-side tool calls the + * provider reported inline. The response body is parsed once here and the parsed tree is reused + * for both, so callers should route all response tagging through this method rather than + * parsing themselves. + */ @SneakyThrows public static void tagLLMSpanResponse( + @Nonnull Tracer tracer, Span span, @Nonnull String providerName, @Nonnull String responseBody, @Nullable Long timeToFirstTokenNanoseconds) { + JsonNode responseJson = BraintrustJsonMapper.get().readTree(responseBody); switch (providerName) { case PROVIDER_NAME_OPENAI -> - tagOpenAIResponse(span, responseBody, timeToFirstTokenNanoseconds); + tagOpenAIResponse(span, responseJson, timeToFirstTokenNanoseconds); case PROVIDER_NAME_ANTHROPIC -> - tagAnthropicResponse(span, responseBody, timeToFirstTokenNanoseconds); + tagAnthropicResponse( + span, responseBody, responseJson, timeToFirstTokenNanoseconds); case PROVIDER_NAME_BEDROCK -> - tagBedrockResponse(span, responseBody, timeToFirstTokenNanoseconds); - default -> tagOpenAIResponse(span, responseBody, timeToFirstTokenNanoseconds); + tagBedrockResponse(span, responseJson, timeToFirstTokenNanoseconds); + default -> tagOpenAIResponse(span, responseJson, timeToFirstTokenNanoseconds); } + addServerSideChildSpans(tracer, span, providerName, responseJson); } public static void tagLLMSpanResponse(Span span, @Nonnull Throwable responseError) { @@ -103,6 +122,43 @@ public static void tagLLMSpanResponse(Span span, @Nonnull Throwable responseErro span.recordException(responseError); } + /** + * Emit child {@code type:"tool"} spans for built-in tool calls the vendor executed server + * side (web search, file search, code interpreter, image generation, remote MCP) that the + * provider reports inline with the LLM response. These are otherwise invisible on the trace — + * unlike client-side tool calls (a plain {@code function_call}/{@code computer_call}), which + * the caller executes and which get instrumented where they run — so they are surfaced as + * children of the LLM span. + * + *

    Providers don't report per-tool timing, so each child will start some time within the llm + * span with a duration of zero + * + *

    Safe to call for any response — non-matching payloads simply yield no spans. + * + *

    Package-private: callers reach this via {@link #tagLLMSpanResponse}, which parses the + * response body once and passes the tree here. Kept accessible for same-package unit tests. + * + * @param tracer used to create the child spans; cannot be derived from {@code llmSpan} + * @param llmSpan the parent LLM span the children are nested under + */ + static void addServerSideChildSpans( + @Nonnull Tracer tracer, + @Nonnull Span llmSpan, + @Nonnull String providerName, + @Nonnull JsonNode responseJson) { + Context parentContext = Context.current().with(llmSpan); + switch (providerName) { + case PROVIDER_NAME_OPENAI -> + addOpenAIServerSideChildSpans(tracer, parentContext, responseJson); + case PROVIDER_NAME_ANTHROPIC -> + addAnthropicServerSideChildSpans(tracer, parentContext, responseJson); + default -> { + // Unknown provider: no server-side tool schema to parse, so emit nothing rather + // than guessing at a response shape. + } + } + } + // ------------------------------------------------------------------------- // OpenAI provider implementation // ------------------------------------------------------------------------- @@ -142,9 +198,7 @@ private static void tagOpenAIRequest( @SneakyThrows private static void tagOpenAIResponse( - Span span, String responseBody, @Nullable Long timeToFirstTokenNanoseconds) { - JsonNode responseJson = BraintrustJsonMapper.get().readTree(responseBody); - + Span span, JsonNode responseJson, @Nullable Long timeToFirstTokenNanoseconds) { // Output — chat completions API uses "choices"; Responses API uses "output" if (responseJson.has("choices")) { span.setAttribute("braintrust.output_json", toJson(responseJson.get("choices"))); @@ -207,6 +261,159 @@ private static void tagOpenAIResponse( } } + private static final String TYPE_TOOL_JSON = "{\"type\":\"tool\"}"; + + /** + * OpenAI Responses {@code output} item types the vendor executes server side, mapped + * to the item fields that make up the tool span's input. Client-side calls ({@code + * function_call}, {@code computer_call}) are intentionally excluded — they run in the caller + * and are instrumented there. {@code image_generation_call} is also excluded: its result + * carries a large base64 image blob that would flow unredacted into the span output, and it has + * no useful input fields to surface. + */ + private static final Map> OPENAI_SERVER_SIDE_ITEM_INPUT_KEYS = + Map.of( + "web_search_call", List.of("action"), + "file_search_call", List.of("queries"), + "code_interpreter_call", List.of("code", "container_id"), + "mcp_call", List.of("arguments")); + + private static void addOpenAIServerSideChildSpans( + Tracer tracer, Context parentContext, JsonNode responseJson) { + try { + JsonNode output = responseJson.get("output"); + if (output == null || !output.isArray()) { + return; + } + for (JsonNode item : output) { + if (!item.isObject()) { + continue; + } + String type = item.path("type").asText(null); + if (type == null || !OPENAI_SERVER_SIDE_ITEM_INPUT_KEYS.containsKey(type)) { + continue; + } + emitOpenAIServerSideToolSpan(tracer, parentContext, item, type); + } + } catch (Exception e) { + log.debug("Could not emit OpenAI server-side child spans", e); + } + } + + private static void emitOpenAIServerSideToolSpan( + Tracer tracer, Context parentContext, JsonNode item, String type) { + // Zero-duration marker: stamp start and end at the same instant. + Instant now = Instant.now(); + Span span = + tracer.spanBuilder(openAIToolSpanName(item, type)) + .setParent(parentContext) + .setStartTimestamp(now) + .startSpan(); + try { + span.setAttribute("braintrust.span_attributes", TYPE_TOOL_JSON); + + JsonNode input = openAIToolSpanInput(item, type); + if (input != null && !input.isNull()) { + span.setAttribute("braintrust.input_json", toJson(input)); + } + String metadata = openAIToolSpanMetadata(item, type); + if (metadata != null) { + span.setAttribute("braintrust.metadata", metadata); + } + + // When the tool call errored, record the error and skip output. + JsonNode error = openAIToolSpanError(item); + if (error != null) { + span.setStatus( + StatusCode.ERROR, error.isValueNode() ? error.asText() : error.toString()); + return; + } + JsonNode output = openAIToolSpanOutput(item, type); + if (output != null) { + span.setAttribute("braintrust.output_json", toJson(output)); + } + } catch (Exception e) { + log.debug("Could not tag OpenAI server-side tool span", e); + } finally { + span.end(now); + } + } + + private static String openAIToolSpanName(JsonNode item, String type) { + String serverLabel = nonEmptyText(item, "server_label"); + String name = nonEmptyText(item, "name"); + if (serverLabel != null && name != null) { + return serverLabel + "." + name; + } + if (name != null) { + return name; + } + return type; + } + + private static JsonNode openAIToolSpanInput(JsonNode item, String type) { + List inputKeys = OPENAI_SERVER_SIDE_ITEM_INPUT_KEYS.get(type); + if (inputKeys.isEmpty()) { + return null; + } + ObjectNode inputData = BraintrustJsonMapper.get().createObjectNode(); + for (String key : inputKeys) { + JsonNode value = item.get(key); + if (value != null && !value.isNull()) { + inputData.set(key, maybeParseJsonString(value)); + } + } + if (inputData.isEmpty()) { + return null; + } + // MCP calls carry a single `arguments` blob — unwrap it to the bare value. + if (inputKeys.size() == 1 && "arguments".equals(inputKeys.get(0))) { + return inputData.get("arguments"); + } + return inputData; + } + + private static JsonNode openAIToolSpanOutput(JsonNode item, String type) { + Set excluded = + new HashSet<>(Set.of("id", "type", "name", "call_id", "server_label", "error")); + excluded.addAll(OPENAI_SERVER_SIDE_ITEM_INPUT_KEYS.get(type)); + + ObjectNode output = BraintrustJsonMapper.get().createObjectNode(); + var fields = item.fields(); + while (fields.hasNext()) { + var entry = fields.next(); + String key = entry.getKey(); + JsonNode value = entry.getValue(); + if (excluded.contains(key) || value == null || value.isNull()) { + continue; + } + if ("output".equals(key)) { + output.set(key, maybeParseJsonString(value)); + } else { + output.set(key, value); + } + } + return output.isEmpty() ? null : output; + } + + private static JsonNode openAIToolSpanError(JsonNode item) { + JsonNode error = item.get("error"); + if (error == null || error.isNull()) { + return null; + } + return maybeParseJsonString(error); + } + + private static String openAIToolSpanMetadata(JsonNode item, String type) { + ObjectNode md = BraintrustJsonMapper.get().createObjectNode(); + md.put("tool_type", type); + putIfPresent(md, "tool_id", item.get("id")); + putIfPresent(md, "call_id", item.get("call_id")); + putIfPresent(md, "status", item.get("status")); + putIfPresent(md, "server_label", item.get("server_label")); + return md.isEmpty() ? null : toJson(md); + } + // ------------------------------------------------------------------------- // Anthropic provider implementation // ------------------------------------------------------------------------- @@ -263,9 +470,10 @@ private static void tagAnthropicRequest( @SneakyThrows private static void tagAnthropicResponse( - Span span, String responseBody, @Nullable Long timeToFirstTokenNanoseconds) { - JsonNode responseJson = BraintrustJsonMapper.get().readTree(responseBody); - + Span span, + String responseBody, + JsonNode responseJson, + @Nullable Long timeToFirstTokenNanoseconds) { // Anthropic response is the full Message object — output it whole span.setAttribute("braintrust.output_json", responseBody); @@ -300,6 +508,19 @@ private static void tagAnthropicResponse( metrics.put("prompt_cache_creation_tokens", cacheCreationTokens); } } + + // Server-side tool usage counts (e.g. web_search_requests, web_fetch_requests). + // Each numeric field becomes a server_tool_use_ metric the backend prices — + // this is how web search cost is attributed. + if (usage.has("server_tool_use") && usage.get("server_tool_use").isObject()) { + var fields = usage.get("server_tool_use").fields(); + while (fields.hasNext()) { + var entry = fields.next(); + if (entry.getValue().isNumber()) { + metrics.put("server_tool_use_" + entry.getKey(), entry.getValue()); + } + } + } } if (!metrics.isEmpty()) { @@ -339,6 +560,266 @@ private static boolean addPerTtlCacheMetrics(Map metrics, JsonNo return emitted; } + private static final String ANTHROPIC_SERVER_TOOL_USE_TYPE = "server_tool_use"; + private static final String ANTHROPIC_TOOL_RESULT_SUFFIX = "_tool_result"; + + /** + * Emit child tool spans for Anthropic server-side tool use. In the Message {@code content} + * array these appear as a {@code server_tool_use} block (the call) and a matching {@code + * *_tool_result} block (e.g. {@code web_search_tool_result}), linked by {@code id} /{@code + * tool_use_id}. Calls and results are paired (buffering results that arrive before their call); + * unmatched calls and results each still get a span. Mirrors the Python SDK's {@code + * _log_server_tool_spans}. + */ + private static void addAnthropicServerSideChildSpans( + Tracer tracer, Context parentContext, JsonNode responseJson) { + try { + JsonNode content = responseJson.get("content"); + if (content == null || !content.isArray()) { + return; + } + Map callsById = new java.util.LinkedHashMap<>(); + Map> pendingResultsById = new java.util.LinkedHashMap<>(); + Set matchedCallIds = new HashSet<>(); + List pairs = new java.util.ArrayList<>(); // {call, result}, either nullable + + for (JsonNode item : content) { + if (!item.isObject()) { + continue; + } + String itemType = item.path("type").asText(null); + if (ANTHROPIC_SERVER_TOOL_USE_TYPE.equals(itemType)) { + JsonNode id = item.get("id"); + if (id != null && id.isTextual()) { + callsById.put(id.asText(), item); + List pending = pendingResultsById.remove(id.asText()); + if (pending != null) { + for (JsonNode result : pending) { + pairs.add(new JsonNode[] {item, result}); + matchedCallIds.add(id.asText()); + } + } + } else { + pairs.add(new JsonNode[] {item, null}); + } + } else if (isAnthropicToolResultType(itemType)) { + JsonNode toolUseId = item.get("tool_use_id"); + if (toolUseId != null && toolUseId.isTextual()) { + if (callsById.containsKey(toolUseId.asText())) { + pairs.add(new JsonNode[] {callsById.get(toolUseId.asText()), item}); + matchedCallIds.add(toolUseId.asText()); + } else { + pendingResultsById + .computeIfAbsent( + toolUseId.asText(), k -> new java.util.ArrayList<>()) + .add(item); + } + } else { + pairs.add(new JsonNode[] {null, item}); + } + } + } + + for (JsonNode[] pair : pairs) { + emitAnthropicServerToolSpan(tracer, parentContext, pair[0], pair[1]); + } + for (Map.Entry entry : callsById.entrySet()) { + if (!matchedCallIds.contains(entry.getKey())) { + emitAnthropicServerToolSpan(tracer, parentContext, entry.getValue(), null); + } + } + for (List pending : pendingResultsById.values()) { + for (JsonNode result : pending) { + emitAnthropicServerToolSpan(tracer, parentContext, null, result); + } + } + } catch (Exception e) { + log.debug("Could not emit Anthropic server-side child spans", e); + } + } + + private static boolean isAnthropicToolResultType(@Nullable String type) { + return type != null + && type.endsWith(ANTHROPIC_TOOL_RESULT_SUFFIX) + && !type.equals("tool_result"); + } + + private static void emitAnthropicServerToolSpan( + Tracer tracer, + Context parentContext, + @Nullable JsonNode call, + @Nullable JsonNode result) { + // Zero-duration marker: stamp start and end at the same instant. + Instant now = Instant.now(); + Span span = + tracer.spanBuilder(anthropicToolSpanName(call, result)) + .setParent(parentContext) + .setStartTimestamp(now) + .startSpan(); + try { + span.setAttribute("braintrust.span_attributes", TYPE_TOOL_JSON); + + JsonNode input = anthropicToolSpanInput(call); + if (input != null && !input.isNull()) { + span.setAttribute("braintrust.input_json", toJson(input)); + } + String metadata = anthropicToolSpanMetadata(call, result); + if (metadata != null) { + span.setAttribute("braintrust.metadata", metadata); + } + + JsonNode output = anthropicToolSpanOutput(result); + if (output == null || output.isNull()) { + return; // no result content — input + metadata only (matches Python) + } + String error = anthropicToolSpanError(result); + if (error != null) { + span.setStatus(StatusCode.ERROR, error); + } + span.setAttribute("braintrust.output_json", toJson(output)); + } catch (Exception e) { + log.debug("Could not tag Anthropic server-side tool span", e); + } finally { + span.end(now); + } + } + + private static String anthropicToolSpanName( + @Nullable JsonNode call, @Nullable JsonNode result) { + if (call != null) { + JsonNode name = call.get("name"); + if (name != null && name.isTextual()) { + return name.asText(); + } + } + if (result != null) { + JsonNode type = result.get("type"); + if (type != null + && type.isTextual() + && type.asText().endsWith(ANTHROPIC_TOOL_RESULT_SUFFIX)) { + String t = type.asText(); + return t.substring(0, t.length() - ANTHROPIC_TOOL_RESULT_SUFFIX.length()); + } + } + return "server_tool"; + } + + private static final Set ANTHROPIC_CALL_INPUT_EXCLUDED = + Set.of("id", "type", "name", "caller"); + + private static JsonNode anthropicToolSpanInput(@Nullable JsonNode call) { + if (call == null) { + return null; + } + JsonNode input = call.get("input"); + if (input != null && !input.isNull()) { + return input; + } + ObjectNode obj = BraintrustJsonMapper.get().createObjectNode(); + var fields = call.fields(); + while (fields.hasNext()) { + var entry = fields.next(); + if (!ANTHROPIC_CALL_INPUT_EXCLUDED.contains(entry.getKey())) { + obj.set(entry.getKey(), entry.getValue()); + } + } + return obj.isEmpty() ? null : obj; + } + + private static final Set ANTHROPIC_RESULT_OUTPUT_EXCLUDED = + Set.of("tool_use_id", "type", "caller"); + + private static JsonNode anthropicToolSpanOutput(@Nullable JsonNode result) { + if (result == null) { + return null; + } + if (result.has("content")) { + return redactServerToolOutput(result.get("content")); + } + ObjectNode obj = BraintrustJsonMapper.get().createObjectNode(); + var fields = result.fields(); + while (fields.hasNext()) { + var entry = fields.next(); + if (!ANTHROPIC_RESULT_OUTPUT_EXCLUDED.contains(entry.getKey())) { + obj.set(entry.getKey(), redactServerToolOutput(entry.getValue())); + } + } + return obj.isEmpty() ? null : obj; + } + + /** Recursively replace {@code encrypted_content} values (opaque, large) with a placeholder. */ + private static JsonNode redactServerToolOutput(JsonNode value) { + if (value == null) { + return null; + } + if (value.isArray()) { + ArrayNode arr = BraintrustJsonMapper.get().createArrayNode(); + for (JsonNode item : value) { + arr.add(redactServerToolOutput(item)); + } + return arr; + } + if (value.isObject()) { + ObjectNode obj = BraintrustJsonMapper.get().createObjectNode(); + var fields = value.fields(); + while (fields.hasNext()) { + var entry = fields.next(); + if ("encrypted_content".equals(entry.getKey())) { + obj.put(entry.getKey(), ""); + } else { + obj.set(entry.getKey(), redactServerToolOutput(entry.getValue())); + } + } + return obj; + } + return value; + } + + private static String anthropicToolSpanError(@Nullable JsonNode result) { + if (result == null) { + return null; + } + JsonNode content = result.get("content"); + if (content == null || !content.isObject()) { + return null; + } + JsonNode type = content.get("type"); + if (type == null || !type.isTextual() || !type.asText().endsWith("_error")) { + return null; + } + JsonNode message = content.get("error_message"); + if (message != null && message.isTextual() && !message.asText().isEmpty()) { + return message.asText(); + } + JsonNode code = content.get("error_code"); + if (code != null && code.isTextual() && !code.asText().isEmpty()) { + return code.asText(); + } + return type.asText(); + } + + private static String anthropicToolSpanMetadata( + @Nullable JsonNode call, @Nullable JsonNode result) { + ObjectNode md = BraintrustJsonMapper.get().createObjectNode(); + JsonNode toolUseId = call != null ? call.get("id") : null; + if (toolUseId == null || toolUseId.isNull()) { + toolUseId = result != null ? result.get("tool_use_id") : null; + } + putIfPresent(md, "tool_use_id", toolUseId); + if (call != null) { + putIfPresent(md, "tool_call_type", call.get("type")); + } + if (result != null) { + putIfPresent(md, "tool_result_type", result.get("type")); + } + JsonNode caller = call != null ? call.get("caller") : null; + if (caller == null || caller.isNull()) { + caller = result != null ? result.get("caller") : null; + } + putIfPresent(md, "caller", caller); + return md.isEmpty() ? null : toJson(md); + } + // ------------------------------------------------------------------------- // AWS Bedrock provider implementation // ------------------------------------------------------------------------- @@ -404,9 +885,7 @@ private static void tagBedrockRequest( @SneakyThrows private static void tagBedrockResponse( - Span span, String responseBody, @Nullable Long timeToFirstTokenNanoseconds) { - JsonNode responseJson = BraintrustJsonMapper.get().readTree(responseBody); - + Span span, JsonNode responseJson, @Nullable Long timeToFirstTokenNanoseconds) { // Bedrock output lives at output.message. Normalize to a single-element array matching the // same [{role, content: [...]}] shape as input so the UI can render the LLM thread view. if (responseJson.has("output") && responseJson.get("output").has("message")) { @@ -439,6 +918,42 @@ private static void tagBedrockResponse( // Shared helpers // ------------------------------------------------------------------------- + /** + * If {@code value} is a string that looks like JSON (starts with {@code [} or {@code {}), parse + * and return it as a tree; otherwise return it unchanged. + */ + private static JsonNode maybeParseJsonString(JsonNode value) { + if (value == null || !value.isTextual()) { + return value; + } + String stripped = value.asText().strip(); + if (stripped.isEmpty() || (stripped.charAt(0) != '[' && stripped.charAt(0) != '{')) { + return value; + } + try { + return BraintrustJsonMapper.get().readTree(stripped); + } catch (Exception e) { + return value; + } + } + + private static void putIfPresent(ObjectNode target, String key, JsonNode value) { + if (value != null && !value.isNull()) { + target.set(key, value); + } + } + + private static String nonEmptyText(JsonNode item, String field) { + JsonNode node = item.get(field); + if (node != null && node.isValueNode()) { + String text = node.asText(); + if (!text.isEmpty()) { + return text; + } + } + return null; + } + /** * Simplifies an Anthropic message node by converting single-text content block arrays (e.g. * {@code [{"type":"text","text":"hello"}]}) to plain strings. This normalizes the format used diff --git a/braintrust-sdk/src/test/java/dev/braintrust/instrumentation/InstrumentationSemConvTest.java b/braintrust-sdk/src/test/java/dev/braintrust/instrumentation/InstrumentationSemConvTest.java new file mode 100644 index 00000000..a6951500 --- /dev/null +++ b/braintrust-sdk/src/test/java/dev/braintrust/instrumentation/InstrumentationSemConvTest.java @@ -0,0 +1,351 @@ +package dev.braintrust.instrumentation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import dev.braintrust.json.BraintrustJsonMapper; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Covers {@link InstrumentationSemConv#addServerSideChildSpans}: only vendor-executed (server-side) + * tool calls in an OpenAI Responses body become child spans nested under the LLM span. Client-side + * calls ({@code function_call}, {@code computer_call}) are excluded. + */ +class InstrumentationSemConvTest { + + private static final AttributeKey SPAN_ATTRIBUTES = + AttributeKey.stringKey("braintrust.span_attributes"); + private static final AttributeKey INPUT_JSON = + AttributeKey.stringKey("braintrust.input_json"); + private static final AttributeKey OUTPUT_JSON = + AttributeKey.stringKey("braintrust.output_json"); + private static final AttributeKey METADATA = + AttributeKey.stringKey("braintrust.metadata"); + + private InMemorySpanExporter exporter; + private SdkTracerProvider tracerProvider; + private Tracer tracer; + + @BeforeEach + void setUp() { + exporter = InMemorySpanExporter.create(); + tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build(); + tracer = tracerProvider.get("test"); + } + + @AfterEach + void tearDown() { + tracerProvider.close(); + } + + /** Runs the emitter with an LLM parent span and returns only the emitted child spans. */ + private List emitOpenAI(String responseBody) { + return emit(InstrumentationSemConv.PROVIDER_NAME_OPENAI, responseBody); + } + + private List emit(String provider, String responseBody) { + Span parent = tracer.spanBuilder("llm").startSpan(); + try (var ignored = parent.makeCurrent()) { + InstrumentationSemConv.addServerSideChildSpans( + tracer, parent, provider, json(responseBody)); + } finally { + parent.end(); + } + return exporter.getFinishedSpanItems().stream() + .filter(s -> !s.getName().equals("llm")) + .collect(Collectors.toList()); + } + + private static SpanData byName(List spans, String name) { + return spans.stream().filter(s -> s.getName().equals(name)).findFirst().orElseThrow(); + } + + private static JsonNode json(String s) { + return BraintrustJsonMapper.fromJson(s, JsonNode.class); + } + + @Test + void emitsWebSearchCallToolSpanParentedToLlm() { + String body = + """ + { + "id": "resp_1", + "output": [ + { + "id": "ws_1", + "type": "web_search_call", + "status": "completed", + "action": {"type": "search", "query": "braintrust observability"} + }, + { + "id": "msg_1", + "type": "message", + "content": [{"type": "output_text", "text": "here is the answer"}] + } + ], + "usage": {"input_tokens": 10, "output_tokens": 20} + } + """; + + List tools = emitOpenAI(body); + // Only the web_search_call becomes a tool span; the message item is ignored. + assertEquals(1, tools.size()); + + SpanData ws = byName(tools, "web_search_call"); + // Parented to the LLM span. + SpanData llm = byName(exporter.getFinishedSpanItems(), "llm"); + assertEquals(llm.getSpanId(), ws.getParentSpanId()); + + assertEquals("{\"type\":\"tool\"}", ws.getAttributes().get(SPAN_ATTRIBUTES)); + + // action is the sole input key -> object with the action content. + JsonNode input = json(ws.getAttributes().get(INPUT_JSON)); + assertEquals("search", input.path("action").path("type").asText()); + assertEquals("braintrust observability", input.path("action").path("query").asText()); + + // metadata carries tool_type / tool_id / status. + JsonNode metadata = json(ws.getAttributes().get(METADATA)); + assertEquals("web_search_call", metadata.path("tool_type").asText()); + assertEquals("ws_1", metadata.path("tool_id").asText()); + assertEquals("completed", metadata.path("status").asText()); + + // action is an input key and id/type/error are excluded; the remaining `status` field + // flows into output (matching the Python SDK). + JsonNode output = json(ws.getAttributes().get(OUTPUT_JSON)); + assertEquals("completed", output.path("status").asText()); + + // Zero-duration marker — providers don't report per-tool timing, so we stamp start and end + // at the same instant rather than fabricate a duration. + assertEquals(ws.getStartEpochNanos(), ws.getEndEpochNanos()); + } + + @Test + void clientSideCallsAreNotSpanned() { + // function_call and computer_call are executed by the caller, not the vendor — no spans. + String body = + """ + { + "output": [ + { + "id": "fc_1", + "type": "function_call", + "call_id": "call_1", + "name": "get_weather", + "arguments": "{\\"city\\": \\"sf\\"}" + }, + { + "id": "cc_1", + "type": "computer_call", + "action": {"type": "screenshot"} + } + ] + } + """; + assertTrue(emitOpenAI(body).isEmpty()); + } + + @Test + void imageGenerationCallIsNotSpanned() { + // image_generation_call is server-side but excluded: its result is a large base64 image + // blob that would flow unredacted into the span output, and it has no useful input. + String body = + """ + { + "output": [ + { + "id": "ig_1", + "type": "image_generation_call", + "status": "completed", + "result": "iVBORw0KGgoAAAANSUhEUgAAA-base64-image-data" + } + ] + } + """; + assertTrue(emitOpenAI(body).isEmpty()); + } + + @Test + void errorItemSetsErrorStatusAndSkipsOutput() { + String body = + """ + { + "output": [ + { + "id": "ws_2", + "type": "web_search_call", + "status": "failed", + "action": {"query": "x"}, + "error": "rate_limited" + } + ] + } + """; + + List tools = emitOpenAI(body); + assertEquals(1, tools.size()); + SpanData ws = tools.get(0); + assertEquals(StatusCode.ERROR, ws.getStatus().getStatusCode()); + assertNull(ws.getAttributes().get(OUTPUT_JSON)); + } + + @Test + void mcpCallUsesServerLabelDottedNameAndUnwrapsArguments() { + String body = + """ + { + "output": [ + { + "id": "mcp_1", + "type": "mcp_call", + "server_label": "deepwiki", + "name": "ask_question", + "arguments": "{\\"q\\": \\"hi\\"}", + "output": "an answer" + } + ] + } + """; + + List tools = emitOpenAI(body); + assertEquals(1, tools.size()); + SpanData mcp = byName(tools, "deepwiki.ask_question"); + // arguments (sole input key) unwrapped to the bare, parsed value. + JsonNode input = json(mcp.getAttributes().get(INPUT_JSON)); + assertEquals("hi", input.path("q").asText()); + // output present (mcp_call is server-side) and JSON-parsed where applicable. + assertEquals( + "an answer", json(mcp.getAttributes().get(OUTPUT_JSON)).path("output").asText()); + } + + @Test + void nonToolResponseEmitsNothing() { + // A Chat Completions body (no `output` array) yields no child spans. + String body = + """ + {"choices": [{"message": {"content": "hi"}}], "usage": {"total_tokens": 5}} + """; + assertTrue(emitOpenAI(body).isEmpty()); + } + + // ------------------------------------------------------------------------- + // Anthropic server-side tool spans + // ------------------------------------------------------------------------- + + /** Runs the Anthropic emitter with an LLM parent span and returns only the child spans. */ + private List emitAnthropic(String responseBody) { + return emit(InstrumentationSemConv.PROVIDER_NAME_ANTHROPIC, responseBody); + } + + @Test + void pairsServerToolUseWithResultAndRedactsEncryptedContent() { + String body = + """ + { + "content": [ + {"type": "server_tool_use", "id": "srv_1", "name": "web_search", + "input": {"query": "braintrust"}}, + {"type": "web_search_tool_result", "tool_use_id": "srv_1", + "content": [{"type": "web_search_result", "title": "BT", + "encrypted_content": "SECRET-BLOB"}]}, + {"type": "text", "text": "here is the answer"} + ] + } + """; + + List tools = emitAnthropic(body); + assertEquals(1, tools.size()); + SpanData ws = byName(tools, "web_search"); + + // Parented to the LLM span. + SpanData llm = byName(exporter.getFinishedSpanItems(), "llm"); + assertEquals(llm.getSpanId(), ws.getParentSpanId()); + assertEquals("{\"type\":\"tool\"}", ws.getAttributes().get(SPAN_ATTRIBUTES)); + + // input = the call's `input`. + assertEquals("braintrust", json(ws.getAttributes().get(INPUT_JSON)).path("query").asText()); + + // metadata carries the pairing identity. + JsonNode md = json(ws.getAttributes().get(METADATA)); + assertEquals("srv_1", md.path("tool_use_id").asText()); + assertEquals("server_tool_use", md.path("tool_call_type").asText()); + assertEquals("web_search_tool_result", md.path("tool_result_type").asText()); + + // output = the result content, with encrypted_content redacted. + JsonNode output = json(ws.getAttributes().get(OUTPUT_JSON)); + assertEquals("BT", output.get(0).path("title").asText()); + assertEquals("", output.get(0).path("encrypted_content").asText()); + } + + @Test + void serverToolResultErrorSetsErrorStatus() { + String body = + """ + { + "content": [ + {"type": "server_tool_use", "id": "srv_2", "name": "web_search", + "input": {"query": "x"}}, + {"type": "web_search_tool_result", "tool_use_id": "srv_2", + "content": {"type": "web_search_tool_result_error", + "error_code": "max_uses_exceeded"}} + ] + } + """; + + List tools = emitAnthropic(body); + assertEquals(1, tools.size()); + assertEquals(StatusCode.ERROR, tools.get(0).getStatus().getStatusCode()); + } + + @Test + void unmatchedServerToolUseStillEmitsSpanWithoutOutput() { + // A call with no matching *_tool_result — input + metadata, no output. + String body = + """ + { + "content": [ + {"type": "server_tool_use", "id": "srv_3", "name": "web_search", + "input": {"query": "y"}} + ] + } + """; + + List tools = emitAnthropic(body); + assertEquals(1, tools.size()); + SpanData ws = tools.get(0); + assertEquals("web_search", ws.getName()); + assertNull(ws.getAttributes().get(OUTPUT_JSON)); + assertEquals("y", json(ws.getAttributes().get(INPUT_JSON)).path("query").asText()); + } + + @Test + void clientToolUseAndPlainTextEmitNothing() { + // Client-side tool_use blocks and text are not server-side tools. + String body = + """ + { + "content": [ + {"type": "tool_use", "id": "t1", "name": "get_weather", "input": {"city": "sf"}}, + {"type": "text", "text": "hi"} + ] + } + """; + assertTrue(emitAnthropic(body).isEmpty()); + } +} diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java index 387ed177..8b07ada5 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpanValidator.java @@ -19,8 +19,9 @@ *

  • {@code input} — input messages *
  • {@code output} — output choices / content *
  • {@code child_spans} — a nested list of child-span assertions, validated recursively against - * the span's actual children (built by {@link SpanFetcher#buildSpanTree}). Handled by the - * generic recursion below; a span that omits it asserts nothing about its children. + * the span's actual children (built by {@link SpanFetcher#buildSpanTree}) by {@link + * #validateChildSpans}, so every level honors the same per-span rules as the top level. A + * span that omits it asserts nothing about its children. * * *

    Spans arrive here already in brainstore format, produced either by {@link SpanConverter} @@ -72,10 +73,60 @@ private static void validateSpan( } Object expectedValue = entry.getValue(); Object actualValue = actual.get(field); + if (CHILD_SPANS_FIELD.equals(field)) { + validateChildSpans(actualValue, expectedValue, context + "." + field); + continue; + } validateValue(actualValue, expectedValue, context + "." + field); } } + /** The nested child-span assertion list a spec may attach to any span. */ + private static final String CHILD_SPANS_FIELD = "child_spans"; + + /** + * Validate nested child-span assertions against the span's actual children (built by {@link + * SpanFetcher#buildSpanTree}). Each child is routed back through {@link #validateSpan} rather + * than the generic map recursion so that every level of the tree honors {@link #SKIPPED_FIELDS} + * — a {@code context:} block on a child span must be skipped just like one on a top-level span, + * otherwise the child assertion fails on a field the Java SDK does not emit yet. As with the + * top-level list, extra actual children are allowed; order is significant. + */ + @SuppressWarnings("unchecked") + private static void validateChildSpans(Object actual, Object expected, String context) { + if (!(expected instanceof List)) { + fail(context + ": spec must supply a list of child-span assertions, got " + expected); + } + List expList = (List) expected; + if (!(actual instanceof List)) { + fail( + String.format( + "%s: expected a List but got %s (value: %s)", + context, + actual == null ? "null" : actual.getClass().getSimpleName(), + actual)); + } + List actualList = (List) actual; + if (actualList.size() < expList.size()) { + fail( + String.format( + "%s: expected at least %d child spans but got %d. actual=%s", + context, expList.size(), actualList.size(), actualList)); + } + for (int i = 0; i < expList.size(); i++) { + String childCtx = context + "[" + i + "]"; + Object actualChild = actualList.get(i); + Object expectedChild = expList.get(i); + if (!(actualChild instanceof Map) || !(expectedChild instanceof Map)) { + fail(childCtx + ": child spans must be maps, got " + actualChild); + } + validateSpan( + (Map) actualChild, + (Map) expectedChild, + childCtx); + } + } + private static void validateMap( Map actual, Map expected, String context) { if (actual == null) { diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java index e5eebeb1..9768db00 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/SpecClientRegistry.java @@ -73,10 +73,17 @@ public final class SpecClientRegistry { "springai2-anthropic", "anthropic", Set.of("/v1/messages"), - // Spec-level cache_control block placement isn't expressible - // via ChatModel messages (Spring AI 2.0 models caching through - // AnthropicCacheOptions instead). - Set.of("prompt_caching_5m", "prompt_caching_1h"), + // prompt_caching: cache_control block placement isn't + // expressible via ChatModel messages (Spring AI 2.0 models + // caching through AnthropicCacheOptions instead). + // web_search: the Spring AI framework (not our SDK) silently + // drops the native web_search_20250305 server tool when + // serializing the request (verified: it sends tools=null), so + // no + // search ever runs. Our instrumentation is correct — there is + // simply no server tool use to surface. Only the raw anthropic + // client exercises this spec. + Set.of("prompt_caching_5m", "prompt_caching_1h", "web_search"), new SpecClient.Isolation( "btx.springai2.classpath", "dev.braintrust.sdkspecimpl.springai2.SpringAi2AnthropicSpecClient"))) diff --git a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/SpringAi1AnthropicSpecClient.java b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/SpringAi1AnthropicSpecClient.java index 3ca6cb0d..87a4c03d 100644 --- a/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/SpringAi1AnthropicSpecClient.java +++ b/btx/src/test/java/dev/braintrust/sdkspecimpl/clients/SpringAi1AnthropicSpecClient.java @@ -25,9 +25,20 @@ public String provider() { return "anthropic"; } + /** + * Specs this client cannot express, keyed by spec {@code name}. The limitation is in the Spring + * AI framework, not our instrumentation: Spring AI 1.x's Anthropic response model ({@code + * AnthropicApi.ContentBlock.Type}) has no {@code web_search_tool_result} value, so the + * framework throws {@code HttpMessageNotReadableException} deserializing a web-search response + * — after our HTTP-layer instrumentation has already captured the spans correctly. (Spring AI + * 2.x fails differently: it silently drops the native tool from the request entirely.) Web + * search is therefore only exercised through the raw {@code anthropic} client. + */ + private static final java.util.Set UNSUPPORTED_SPECS = java.util.Set.of("web_search"); + @Override public boolean supports(LlmSpanSpec spec) { - return "/v1/messages".equals(spec.endpoint()); + return "/v1/messages".equals(spec.endpoint()) && !UNSUPPORTED_SPECS.contains(spec.name()); } @Override diff --git a/gradle.properties b/gradle.properties index 52a608c5..a0066932 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.daemon=true org.gradle.warning.mode=summary # braintrust-spec git ref (SHA or tag) used by btx tests -braintrustSpecRef=v0.0.10 +braintrustSpecRef=v0.0.11 # braintrust-openapi commit SHA used by braintrust-api braintrustOpenApiRef=64b79cb9122f50a74eac98ea86c3ec1858c0cdd1