promptfoo 0.125.0 → 0.125.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/src/accounts-BOxNYxVN.js +229 -0
- package/dist/src/accounts-BezXLdAW.cjs +276 -0
- package/dist/src/accounts-CPISMNlh.js +227 -0
- package/dist/src/accounts-vJBPs89d.js +222 -0
- package/dist/src/agentWorkspace-Bom7SpF2.js +592 -0
- package/dist/src/agentWorkspace-CEomAcws.cjs +619 -0
- package/dist/src/agentWorkspace-DPtMLrlI.js +593 -0
- package/dist/src/agentWorkspace-DkAeEYLz.js +592 -0
- package/dist/src/agentic-utils-121v7mJo.js +197 -0
- package/dist/src/agentic-utils-CBWD_rcd.cjs +237 -0
- package/dist/src/agentic-utils-D8wyQwPG.js +198 -0
- package/dist/src/agentic-utils-EP1QkQCs.js +197 -0
- package/dist/src/agents-Br3ah8iA.js +778 -0
- package/dist/src/agents-C5wj_UTv.js +777 -0
- package/dist/src/agents-C9Efp4mC.js +266 -0
- package/dist/src/agents-CQYwaVCO.js +267 -0
- package/dist/src/agents-CjCd4Vo8.js +777 -0
- package/dist/src/agents-DRWiCHYz.js +266 -0
- package/dist/src/agents-F2rhx0iL.cjs +266 -0
- package/dist/src/agents-RTYyy69h.cjs +777 -0
- package/dist/src/agents-api-CY72O86e.js +711 -0
- package/dist/src/agents-api-CsSsC21b.js +710 -0
- package/dist/src/agents-api-DN-ibDc8.js +710 -0
- package/dist/src/agents-api-cNDsCCv1.cjs +710 -0
- package/dist/src/ai.studio-Bj6Dyi6o.js +2 -0
- package/dist/src/ai.studio-C-e0LzyW.js +369 -0
- package/dist/src/ai.studio-CR8M6Aqf.cjs +391 -0
- package/dist/src/ai.studio-YKk7F0tv.js +374 -0
- package/dist/src/aimlapi-BGunsuTu.js +30 -0
- package/dist/src/aimlapi-BkzcfBsL.js +29 -0
- package/dist/src/aimlapi-DXFcEdP8.js +28 -0
- package/dist/src/aimlapi-r-8iCcAw.cjs +29 -0
- package/dist/src/anthropicMessages-7-YNRgQR.js +114 -0
- package/dist/src/anthropicMessages-C8m9Whrd.cjs +114 -0
- package/dist/src/anthropicMessages-DS1vXD0f.js +115 -0
- package/dist/src/anthropicMessages-DmvGkz-0.js +114 -0
- package/dist/src/app/assets/index-CrdXWTXm.js +392 -0
- package/dist/src/app/browser-inventory.json +4 -4
- package/dist/src/app/index.html +1 -1
- package/dist/src/audio-BLgFgyw-.cjs +104 -0
- package/dist/src/audio-C2sibT7Q.js +104 -0
- package/dist/src/audio-DXEXb03H.js +104 -0
- package/dist/src/audio-TkvojpwO.js +105 -0
- package/dist/src/authoritativeMarkupInjection-BAT9B-Wj.js +79 -0
- package/dist/src/authoritativeMarkupInjection-B_yHETKT.js +78 -0
- package/dist/src/authoritativeMarkupInjection-BfjecQ3Q.cjs +78 -0
- package/dist/src/authoritativeMarkupInjection-D-hDKrhD.js +78 -0
- package/dist/src/aws-B_9unI_-.js +1149 -0
- package/dist/src/aws-D0FY3qkc.js +2433 -0
- package/dist/src/aws-Dh1-a90n.js +2433 -0
- package/dist/src/aws-qskKGczY.cjs +2454 -0
- package/dist/src/base-CaWlBkVz.js +462 -0
- package/dist/src/base-CcViNwVs.js +255 -0
- package/dist/src/base-CsVbPJkU.cjs +467 -0
- package/dist/src/base-DNK9py25.js +255 -0
- package/dist/src/base-DlH5tjEZ.js +463 -0
- package/dist/src/base-DyLOZ_iX.js +256 -0
- package/dist/src/base-RRYXfG7l.cjs +305 -0
- package/dist/src/base-oXVgvtVn.js +462 -0
- package/dist/src/bedrock-BP17ZXbR.cjs +2 -0
- package/dist/src/bedrock-CsjM1dOt.js +1300 -0
- package/dist/src/bedrock-CzPwj8GO.js +1 -0
- package/dist/src/bedrock-eGXZw8oh.js +1 -0
- package/dist/src/bedrock-tSWjmXdj.js +2 -0
- package/dist/src/bestOfN-BQ93nUKV.js +160 -0
- package/dist/src/bestOfN-ByGGwlcb.cjs +161 -0
- package/dist/src/bestOfN-CbP7csHr.js +159 -0
- package/dist/src/bestOfN-xn6i2Xs2.js +159 -0
- package/dist/src/cache-CHq9hPz9.js +2 -0
- package/dist/src/cache-D-9eZN3I.js +754 -0
- package/dist/src/cache-DSm6t1MZ.js +741 -0
- package/dist/src/cache-DeF5zIEN.js +782 -0
- package/dist/src/cache-iG87oYan.cjs +856 -0
- package/dist/src/cerebras-CTtM4yMH.js +98 -0
- package/dist/src/cerebras-CogcJ3ce.cjs +97 -0
- package/dist/src/cerebras-DmkRypTh.js +97 -0
- package/dist/src/cerebras-GUp0B8JY.js +97 -0
- package/dist/src/chat-B4ihd3fw.js +226 -0
- package/dist/src/chat-B9cAQ_rS.js +227 -0
- package/dist/src/chat-BU7EB4R-.cjs +76 -0
- package/dist/src/chat-C8yxJ2-s.cjs +226 -0
- package/dist/src/chat-ClTFM3qq.js +934 -0
- package/dist/src/chat-Cnq0Is68.js +123 -0
- package/dist/src/chat-CwrurR57.cjs +987 -0
- package/dist/src/chat-D0lsojPv.js +124 -0
- package/dist/src/chat-D7_8yFIx.js +76 -0
- package/dist/src/chat-DCoETlLG.js +226 -0
- package/dist/src/chat-DHE7xaTR.js +76 -0
- package/dist/src/chat-DmhT6rz2.cjs +123 -0
- package/dist/src/chat-DvfMe8KI.js +123 -0
- package/dist/src/chat-DwP5kVnl.js +935 -0
- package/dist/src/chat-MB7U-QE2.js +77 -0
- package/dist/src/chat-zCWuBKAj.js +934 -0
- package/dist/src/claude-agent-sdk-23bIVI75.js +999 -0
- package/dist/src/claude-agent-sdk-BACpnIJi.cjs +1005 -0
- package/dist/src/claude-agent-sdk-BlYlmcEr.js +999 -0
- package/dist/src/claude-agent-sdk-Cr-jqJZl.js +1000 -0
- package/dist/src/client-BktbiPx9.cjs +357 -0
- package/dist/src/client-CjztEBmW.js +351 -0
- package/dist/src/client-mRs2UvH9.js +351 -0
- package/dist/src/client-uCz0ydjC.js +352 -0
- package/dist/src/cloud-BtYF555L.js +2 -0
- package/dist/src/cloud-CVSUEmAy.js +287 -0
- package/dist/src/cloud-D7Ifb-Fl.js +599 -0
- package/dist/src/cloud-DB5oK5NX.cjs +666 -0
- package/dist/src/cloud-ecu989Em.js +657 -0
- package/dist/src/cloud-ramjGURF.js +599 -0
- package/dist/src/cloudflare-ai-BfDkIjO8.js +133 -0
- package/dist/src/cloudflare-ai-D9HaXz1M.js +134 -0
- package/dist/src/cloudflare-ai-HrD5WUK8.js +132 -0
- package/dist/src/cloudflare-ai-Qdj8aKP2.cjs +133 -0
- package/dist/src/cloudflare-gateway-C81uKETQ.js +276 -0
- package/dist/src/cloudflare-gateway-CX5Z9X0k.js +276 -0
- package/dist/src/cloudflare-gateway-DW-og8gb.js +277 -0
- package/dist/src/cloudflare-gateway-iRyXo9n2.cjs +276 -0
- package/dist/src/codex-app-server-BFIrSgdy.js +2155 -0
- package/dist/src/codex-app-server-By1eDVD4.js +2 -0
- package/dist/src/codex-app-server-DKsF4qmY.js +2156 -0
- package/dist/src/codex-app-server-n7n4uZzC.cjs +2162 -0
- package/dist/src/codex-app-server-xAoELVxu.js +2155 -0
- package/dist/src/codex-sdk-C2ZLxa6r.js +1552 -0
- package/dist/src/codex-sdk-CzG02tQt.cjs +1588 -0
- package/dist/src/codex-sdk-DNxAOhiV.js +2 -0
- package/dist/src/codex-sdk-DT3aQezK.js +1557 -0
- package/dist/src/codex-sdk-pw9DpcJP.js +1557 -0
- package/dist/src/codex-security-B0bLmXJF.js +406 -0
- package/dist/src/codex-security-BhYs-3iY.js +406 -0
- package/dist/src/codex-security-DQcOfxYW.js +407 -0
- package/dist/src/codex-security-DsCWr6GZ.cjs +411 -0
- package/dist/src/cometapi-B16asPLX.cjs +55 -0
- package/dist/src/cometapi-MgTbZA_H.js +53 -0
- package/dist/src/cometapi-i33vKzPV.js +55 -0
- package/dist/src/cometapi-wK2UWK0X.js +56 -0
- package/dist/src/compatible-COj8ZCy9.js +24 -0
- package/dist/src/compatible-CRSsIUGb.cjs +24 -0
- package/dist/src/compatible-Dy1vCcNF.js +25 -0
- package/dist/src/compatible-aUItQrpk.js +24 -0
- package/dist/src/completion-Bn7COFMk.js +119 -0
- package/dist/src/completion-Bub2N48b.js +118 -0
- package/dist/src/completion-Cf2G9OlM.cjs +123 -0
- package/dist/src/conversationScoring-BiTYJ9MD.js +374 -0
- package/dist/src/conversationScoring-CKL47uIu.js +373 -0
- package/dist/src/conversationScoring-DCvQKp6k.cjs +398 -0
- package/dist/src/conversationScoring-DLNJtDZI.js +373 -0
- package/dist/src/crescendo-5iLk9WiJ.cjs +734 -0
- package/dist/src/crescendo-B5ylG7kW.js +733 -0
- package/dist/src/crescendo-BD7k6TIx.js +730 -0
- package/dist/src/crescendo-DqO2Ch0P.js +732 -0
- package/dist/src/custom-8rv_aG2K.js +617 -0
- package/dist/src/custom-BOtrIHFN.js +619 -0
- package/dist/src/custom-BvN8seCs.cjs +620 -0
- package/dist/src/custom-Cld9v_Y4.js +618 -0
- package/dist/src/dataUrl-BdHUey2l.cjs +3187 -0
- package/dist/src/dataUrl-DBMifX6C.js +3133 -0
- package/dist/src/dataUrl-DUSr-ZvI.js +2868 -0
- package/dist/src/decisions-CLazADTw.js +516 -0
- package/dist/src/decisions-D5X93awq.js +515 -0
- package/dist/src/decisions-DmaeArEn.js +515 -0
- package/dist/src/decisions-DyQJyHJz.cjs +514 -0
- package/dist/src/docker-C9NYQFeu.js +94 -0
- package/dist/src/docker-CGq3mMol.cjs +94 -0
- package/dist/src/docker-DOm7nmpl.js +95 -0
- package/dist/src/docker-skFN-Kfo.js +93 -0
- package/dist/src/embedding-0UbzkTk9.js +82 -0
- package/dist/src/embedding-DTrSDt7k.cjs +86 -0
- package/dist/src/embedding-j2ORNl5H.js +81 -0
- package/dist/src/envoy-BvS8XTQO.js +60 -0
- package/dist/src/envoy-Cn6Hzw0e.js +60 -0
- package/dist/src/envoy-DC3KWcUY.cjs +60 -0
- package/dist/src/envoy-DetHjo5B.js +61 -0
- package/dist/src/eval-Cer9dPe9.js +1920 -0
- package/dist/src/eval-CytYmI2Z.js +2 -0
- package/dist/src/evalResult-DzOq5wt9.js +1300 -0
- package/dist/src/evaluator-BxCmvGtv.js +10578 -0
- package/dist/src/evaluator-Ct7YDELm.js +2 -0
- package/dist/src/evaluatorHelpers-B_AwIrsl.js +600 -0
- package/dist/src/evaluatorHelpers-D1Tpc7gd.js +601 -0
- package/dist/src/evaluatorHelpers-DRR3rLq0.cjs +622 -0
- package/dist/src/fetch-B5KxZuKu.js +1559 -0
- package/dist/src/fetch-BG_Jl7ty.js +1556 -0
- package/dist/src/fetch-BKlgUg08.cjs +1853 -0
- package/dist/src/fetch-C9Fsja0R.js +2 -0
- package/dist/src/fetch-CM7P8KWA.cjs +14 -0
- package/dist/src/fetch-D0FkIB3G.js +9 -0
- package/dist/src/fetch-RXHsZF-J.js +1280 -0
- package/dist/src/fetch-rSECy8jR.js +10 -0
- package/dist/src/functionCallbackUtils-BB9tmf6a.cjs +262 -0
- package/dist/src/functionCallbackUtils-BXk2iY01.js +246 -0
- package/dist/src/functionCallbackUtils-B_cYp7P5.js +245 -0
- package/dist/src/functionCallbackUtils-BfXnPo0o.js +245 -0
- package/dist/src/gemini-image-BXLMADDz.js +471 -0
- package/dist/src/gemini-image-BYUZyEUx.js +472 -0
- package/dist/src/gemini-image-DSt1QubZ.cjs +471 -0
- package/dist/src/gemini-image-DdNDMiZ_.js +471 -0
- package/dist/src/goat-9b-uU_k6.js +514 -0
- package/dist/src/goat-BCJuiDd0.js +515 -0
- package/dist/src/goat-CmAHbemm.js +512 -0
- package/dist/src/goat-DdOTdyoS.cjs +515 -0
- package/dist/src/goblin-Bp5C5lyb.js +22 -0
- package/dist/src/goblin-Dmr7GItI.js +23 -0
- package/dist/src/goblin-DpM8wxlf.cjs +22 -0
- package/dist/src/goblin-cJyH6Pq-.js +22 -0
- package/dist/src/google-B6eaPAbE.js +142 -0
- package/dist/src/google-D-FStdN6.cjs +142 -0
- package/dist/src/google-DAC7BsCW.js +142 -0
- package/dist/src/google-Id18H0Va.js +143 -0
- package/dist/src/graders-BjNQWkaB.js +2 -0
- package/dist/src/graders-CqdIp_vl.js +15470 -0
- package/dist/src/hydra-6Ouh74DE.js +610 -0
- package/dist/src/hydra-BHjhZnLB.cjs +612 -0
- package/dist/src/hydra-BL6k2REV.js +2 -0
- package/dist/src/hydra-BWUc0o67.js +613 -0
- package/dist/src/hydra-CUkEOLKw.js +612 -0
- package/dist/src/image-B6hG_xhb.js +481 -0
- package/dist/src/image-BFaq7Krd.cjs +209 -0
- package/dist/src/image-Bql5C0N9.cjs +504 -0
- package/dist/src/image-BtZfI2pT.js +163 -0
- package/dist/src/image-CIM4oN7Z.cjs +163 -0
- package/dist/src/image-CbAl7Jpu.js +209 -0
- package/dist/src/image-CiYm_yFw.js +209 -0
- package/dist/src/image-DCMNvBiV.js +163 -0
- package/dist/src/image-DL7pIuUZ.js +210 -0
- package/dist/src/image-ey0kxF8o.js +164 -0
- package/dist/src/image-smgIk3rE.js +482 -0
- package/dist/src/index.cjs +27 -27
- package/dist/src/index.d.cts +8 -8
- package/dist/src/index.js +27 -27
- package/dist/src/indirectWebPwn-B_-UpWY5.js +285 -0
- package/dist/src/indirectWebPwn-Cz3SLiEq.js +434 -0
- package/dist/src/indirectWebPwn-D2s8zGiM.cjs +463 -0
- package/dist/src/indirectWebPwn-DXbj2wGu.cjs +285 -0
- package/dist/src/indirectWebPwn-D_4H44E6.js +285 -0
- package/dist/src/indirectWebPwn-L044SrpV.js +434 -0
- package/dist/src/indirectWebPwn-cnmvdavH.js +435 -0
- package/dist/src/indirectWebPwn-zUBzsqTt.js +286 -0
- package/dist/src/interactions-Bu3oVQ-j.js +850 -0
- package/dist/src/interactions-CND010Lh.cjs +850 -0
- package/dist/src/interactions-DAx7tqUz.js +851 -0
- package/dist/src/interactions-DPkIumeh.js +850 -0
- package/dist/src/iterative-BO4JRnpG.js +545 -0
- package/dist/src/iterative-CAK-k9rz.js +548 -0
- package/dist/src/iterative-CDgHc0xO.cjs +549 -0
- package/dist/src/iterative-CXJlHLl9.js +547 -0
- package/dist/src/iterativeImage-BEdGKSmh.cjs +419 -0
- package/dist/src/iterativeImage-BfL0QZw5.js +417 -0
- package/dist/src/iterativeImage-C9KhnibC.js +417 -0
- package/dist/src/iterativeImage-btjkOfWG.js +418 -0
- package/dist/src/iterativeMeta-B5Bdw6QA.js +437 -0
- package/dist/src/iterativeMeta-C3mI4gpT.js +440 -0
- package/dist/src/iterativeMeta-Co2BKyqS.cjs +439 -0
- package/dist/src/iterativeMeta-DD8ihRWD.js +439 -0
- package/dist/src/iterativeTree-AMi4GV2O.js +878 -0
- package/dist/src/iterativeTree-C8FJOEje.cjs +879 -0
- package/dist/src/iterativeTree-CvrY1f2Z.js +877 -0
- package/dist/src/iterativeTree-bPtESmaD.js +876 -0
- package/dist/src/knowledgeBase-Cf5871e3.js +163 -0
- package/dist/src/knowledgeBase-D2PQhqLH.js +164 -0
- package/dist/src/knowledgeBase-DpoMw1IH.cjs +163 -0
- package/dist/src/knowledgeBase-npGFl763.js +163 -0
- package/dist/src/litellm-BBB5DQdt.cjs +146 -0
- package/dist/src/litellm-Bo43ow7r.js +145 -0
- package/dist/src/litellm-CaU8CMLS.js +146 -0
- package/dist/src/litellm-wY7Unh8o.js +147 -0
- package/dist/src/live-5g2w-M-E.js +891 -0
- package/dist/src/live-BfaXsDyD.cjs +891 -0
- package/dist/src/live-C1xewnGD.js +2 -0
- package/dist/src/live-DaF7nYy5.js +892 -0
- package/dist/src/live-lp3urLUw.js +891 -0
- package/dist/src/luma-ray-B7zvKGVG.js +309 -0
- package/dist/src/luma-ray-DCf5fnrz.cjs +309 -0
- package/dist/src/luma-ray-DepBcn6z.js +309 -0
- package/dist/src/luma-ray-XQC4M1pt.js +310 -0
- package/dist/src/main.js +26 -26
- package/dist/src/mantleChat-7xm95vSV.js +154 -0
- package/dist/src/mantleChat-CXhAs5RD.js +155 -0
- package/dist/src/mantleChat-CcE-zjfj.cjs +154 -0
- package/dist/src/mantleChat-Cpefl7Zm.js +154 -0
- package/dist/src/mcp-B0V0ikdM.js +84 -0
- package/dist/src/mcp-CV2k2gwj.js +85 -0
- package/dist/src/memoryPoisoning-BC5FQQWZ.js +111 -0
- package/dist/src/memoryPoisoning-BDwrtZKX.js +111 -0
- package/dist/src/memoryPoisoning-BFemS_FF.js +112 -0
- package/dist/src/memoryPoisoning-DRs2rbVV.cjs +111 -0
- package/dist/src/messages-Bc5oZoAJ.js +1128 -0
- package/dist/src/messages-BkaH8fRW.js +1127 -0
- package/dist/src/messages-CoHlziS5.js +1127 -0
- package/dist/src/messages-Cv_m6kYs.cjs +1161 -0
- package/dist/src/mischievousUser-BcyFxrqJ.js +51 -0
- package/dist/src/mischievousUser-BlhOQSd2.js +52 -0
- package/dist/src/mischievousUser-BuQiKE8D.js +51 -0
- package/dist/src/mischievousUser-ChIOk6cT.cjs +51 -0
- package/dist/src/modelslab-BXIWBTgX.js +171 -0
- package/dist/src/modelslab-ChVsUiaJ.js +170 -0
- package/dist/src/modelslab-DZ6BFlDv.js +170 -0
- package/dist/src/modelslab-DhRr-vyo.cjs +170 -0
- package/dist/src/nova-reel-BL8DrN4_.js +272 -0
- package/dist/src/nova-reel-BiOMUm5q.cjs +272 -0
- package/dist/src/nova-reel-CTsya1hV.js +273 -0
- package/dist/src/nova-reel-kgBY4Svd.js +272 -0
- package/dist/src/nova-sonic-CaSSA3Jq.cjs +374 -0
- package/dist/src/nova-sonic-DLpmoGsR.js +375 -0
- package/dist/src/nova-sonic-DwWc4SoN.js +374 -0
- package/dist/src/nova-sonic-qnReTaXm.js +374 -0
- package/dist/src/novita-B5mAt3Ui.js +114 -0
- package/dist/src/novita-D57vCjDD.js +116 -0
- package/dist/src/novita-ITRtG8IJ.js +115 -0
- package/dist/src/novita-Rh3T13YO.cjs +115 -0
- package/dist/src/nscale-8V7iZ_pI.js +210 -0
- package/dist/src/nscale-BQnMv4pH.cjs +209 -0
- package/dist/src/nscale-BWZXHZVF.js +209 -0
- package/dist/src/nscale-BqrHiUqU.js +207 -0
- package/dist/src/openaiResponses-BMtGTu8R.js +317 -0
- package/dist/src/openaiResponses-CvcI_viE.js +318 -0
- package/dist/src/openaiResponses-DYPZEMX1.cjs +318 -0
- package/dist/src/openaiResponses-DZTg1-kb.js +319 -0
- package/dist/src/openclaw-BeKuig4g.js +1338 -0
- package/dist/src/openclaw-COkdQ83y.js +1337 -0
- package/dist/src/openclaw-CSoX_HQ5.cjs +1344 -0
- package/dist/src/openclaw-DJlhPGCb.js +1336 -0
- package/dist/src/opencode-sdk-BEJftEs6.cjs +1018 -0
- package/dist/src/opencode-sdk-DMvBB3V7.js +1013 -0
- package/dist/src/opencode-sdk-Df7hMjLO.js +1012 -0
- package/dist/src/opencode-sdk-bQp5Xf3W.js +1012 -0
- package/dist/src/openinterpreter-B3GgVZjK.js +431 -0
- package/dist/src/openinterpreter-BiGcU-HN.cjs +435 -0
- package/dist/src/openinterpreter-Buj2l3Eh.js +432 -0
- package/dist/src/openinterpreter-C3_Cg5b_.js +431 -0
- package/dist/src/pricing-0Pampf0q.cjs +999 -0
- package/dist/src/pricing-B8VdhEhY.js +976 -0
- package/dist/src/pricing-CtLA-rKA.js +976 -0
- package/dist/src/promptfoo-4OkHtH9P.cjs +341 -0
- package/dist/src/promptfoo-Bhpm7sH6.js +286 -0
- package/dist/src/promptfoo-BqV8ogVA.js +285 -0
- package/dist/src/promptfoo-S1Q9OHk_.js +286 -0
- package/dist/src/providers-B5zs1ZRv.js +24437 -0
- package/dist/src/providers-Cd6CJh6m.js +2 -0
- package/dist/src/providers-CpJlgHET.js +24039 -0
- package/dist/src/providers-CzvLvK6G.cjs +24180 -0
- package/dist/src/quiverai-1E_IvLHn.js +325 -0
- package/dist/src/quiverai-BXH004Vv.js +325 -0
- package/dist/src/quiverai-Ccya2z8Y.cjs +325 -0
- package/dist/src/quiverai-Cez57oe-.js +326 -0
- package/dist/src/registry-3zX_M9Ik.js +131 -0
- package/dist/src/registry-AuCrKTvS.js +132 -0
- package/dist/src/registry-CxXWKtQG.js +131 -0
- package/dist/src/registry-DG3o66JQ.cjs +131 -0
- package/dist/src/remoteGeneration-CAtvamG6.js +370 -0
- package/dist/src/remoteGeneration-CF225pDl.cjs +482 -0
- package/dist/src/remoteGeneration-DtnqocnL.js +370 -0
- package/dist/src/remoteGeneration-g3KvW-sP.js +371 -0
- package/dist/src/responses-BMbaUXQI.js +1098 -0
- package/dist/src/responses-B_TOSof-.js +1099 -0
- package/dist/src/responses-v-yANwTA.cjs +1109 -0
- package/dist/src/runtimeTransform-BBZv-oUZ.js +156 -0
- package/dist/src/runtimeTransform-DT2v5ovW.js +156 -0
- package/dist/src/runtimeTransform-GXXEIXdS.cjs +161 -0
- package/dist/src/runtimeTransform-JTwBQc-P.js +157 -0
- package/dist/src/sagemaker-B8D0L4rj.cjs +716 -0
- package/dist/src/sagemaker-CR2_AGdB.js +713 -0
- package/dist/src/sagemaker-Cd9qSQUV.js +714 -0
- package/dist/src/sagemaker-mKlltN4p.js +713 -0
- package/dist/src/scanner-PG_8lh8r.js +1971 -0
- package/dist/src/server/index.js +55 -55
- package/dist/src/server-Bmh7sd9F.js +155 -0
- package/dist/src/server-C7b8z6aC.js +63 -0
- package/dist/src/server-CAcovgtL.js +2 -0
- package/dist/src/server-CuuMfKlh.cjs +74 -0
- package/dist/src/server-DMPCj9jy.js +189 -0
- package/dist/src/session-BksXvRH_.cjs +69 -0
- package/dist/src/session-DOxUjqGQ.js +63 -0
- package/dist/src/session-Dujsc-b9.js +62 -0
- package/dist/src/session-VjtIxREd.js +62 -0
- package/dist/src/shared-Bkyz_7e5.cjs +2185 -0
- package/dist/src/shared-DDJg8ycC.js +1929 -0
- package/dist/src/shared-Dh3yfRBd.js +1927 -0
- package/dist/src/shared-tfPT8Tna.js +1928 -0
- package/dist/src/simulatedUser-6JdkyL3k.cjs +252 -0
- package/dist/src/simulatedUser-CFr2mr3M.js +248 -0
- package/dist/src/simulatedUser-DjwwUqx-.js +247 -0
- package/dist/src/slack-C_gIYiUs.js +144 -0
- package/dist/src/slack-DhNbz9CM.cjs +144 -0
- package/dist/src/slack-Dm2iI1zu.js +145 -0
- package/dist/src/strategies-BIY-bNul.js +2248 -0
- package/dist/src/strategies-C1KQQj9G.cjs +2420 -0
- package/dist/src/strategies-DM_3mReG.js +2385 -0
- package/dist/src/telemetry-BhMTibZO.js +2 -0
- package/dist/src/telemetry-C04d25BM.cjs +216 -0
- package/dist/src/telemetry-CIqy4fSC.js +201 -0
- package/dist/src/telemetry-CXozp_6C.js +205 -0
- package/dist/src/telemetry-g7PkBxmY.js +205 -0
- package/dist/src/togetherai-BvCim68-.js +50 -0
- package/dist/src/togetherai-CWyDCA9m.cjs +49 -0
- package/dist/src/togetherai-CeSE31vK.js +49 -0
- package/dist/src/togetherai-_2W0URf8.js +48 -0
- package/dist/src/traceContext-Brw3Oe41.js +1016 -0
- package/dist/src/traceContext-DpAYv27G.js +1015 -0
- package/dist/src/traceContext-KSqhzZI2.cjs +1032 -0
- package/dist/src/transcription-BySopazW.js +175 -0
- package/dist/src/transcription-DO4UPkRI.cjs +178 -0
- package/dist/src/transcription-RJGBa-pC.js +176 -0
- package/dist/src/transcription-fZenF3G5.js +175 -0
- package/dist/src/transform-BzhRc5z2.js +125 -0
- package/dist/src/transform-CPQu05u6.cjs +154 -0
- package/dist/src/transform-DAED8imp.js +126 -0
- package/dist/src/transform-DIOIQDmM.js +125 -0
- package/dist/src/typesafe-ByE0Kxfg.js +253 -0
- package/dist/src/typesafe-CFqGFY34.js +253 -0
- package/dist/src/typesafe-DCWIw-CP.cjs +253 -0
- package/dist/src/typesafe-DyHFV5Qk.js +254 -0
- package/dist/src/util-BHkj9joz.js +2049 -0
- package/dist/src/util-CICwIpmu.cjs +934 -0
- package/dist/src/util-CePiY2xZ.js +286 -0
- package/dist/src/util-CgT3uf4p.js +743 -0
- package/dist/src/util-Cj9drvOY.js +1770 -0
- package/dist/src/util-ClJRjlQA.cjs +357 -0
- package/dist/src/util-Cxf8VF2M.js +395 -0
- package/dist/src/util-D7RdV7rS.js +395 -0
- package/dist/src/util-DMNFhk47.js +351 -0
- package/dist/src/util-DNzh3M0z.js +396 -0
- package/dist/src/util-Df7rfMGb.cjs +2305 -0
- package/dist/src/util-Kf0KnG_6.js +2051 -0
- package/dist/src/util-LEbeiYw_.js +743 -0
- package/dist/src/util-WH8CgVs4.js +352 -0
- package/dist/src/util-cx6Xw8Lw.cjs +478 -0
- package/dist/src/util-qzBqirBH.js +2050 -0
- package/dist/src/vertex-CBAMfRz3.js +854 -0
- package/dist/src/vertex-CMP2IpN9.js +2 -0
- package/dist/src/vertex-CZ5YOotT.js +848 -0
- package/dist/src/vertex-DNsh1F1M.cjs +871 -0
- package/dist/src/vertexLive-BTXYj3-F.js +55 -0
- package/dist/src/vertexLive-Ba_LAqEF.cjs +55 -0
- package/dist/src/vertexLive-DM4sFaaj.js +56 -0
- package/dist/src/vertexLive-JqZOw7HC.js +55 -0
- package/dist/src/video-BY7Q2Gii.cjs +706 -0
- package/dist/src/video-CYElP-CK.js +703 -0
- package/dist/src/video-C_AdEDH0.js +702 -0
- package/dist/src/video-DGvjQ_4j.js +702 -0
- package/package.json +1 -1
- package/dist/src/accounts-Dpb43yde.cjs +0 -276
- package/dist/src/accounts-GKrO1olH.js +0 -229
- package/dist/src/accounts-e4FGOsji.js +0 -227
- package/dist/src/accounts-sUiEJfkB.js +0 -222
- package/dist/src/agentWorkspace-B8A_wuMR.cjs +0 -619
- package/dist/src/agentWorkspace-DSPIscXR.js +0 -593
- package/dist/src/agentWorkspace-DxUqd-XK.js +0 -592
- package/dist/src/agentWorkspace-n0uOJXhD.js +0 -592
- package/dist/src/agentic-utils-9bCj4twQ.js +0 -197
- package/dist/src/agentic-utils-CyCLuLoj.js +0 -197
- package/dist/src/agentic-utils-D23qtOlx.cjs +0 -237
- package/dist/src/agentic-utils-DuBMMNCw.js +0 -198
- package/dist/src/agents-6hDRNoYD.js +0 -777
- package/dist/src/agents-B8w2o7Sc.cjs +0 -777
- package/dist/src/agents-Ck0NXl7v.js +0 -777
- package/dist/src/agents-CwT3B1qB.js +0 -778
- package/dist/src/agents-DOLjfOEq.js +0 -266
- package/dist/src/agents-OcF9hq--.js +0 -267
- package/dist/src/agents-api-CnZZt8bn.cjs +0 -710
- package/dist/src/agents-api-D04u3bmL.js +0 -711
- package/dist/src/agents-api-DWw0_Vio.js +0 -710
- package/dist/src/agents-api-IWHEwp8i.js +0 -710
- package/dist/src/agents-mHt2Mh9O.cjs +0 -266
- package/dist/src/agents-sICccZCA.js +0 -266
- package/dist/src/ai.studio-Ci4rypDT.js +0 -2
- package/dist/src/ai.studio-KkQ6KaHa.js +0 -369
- package/dist/src/ai.studio-gSXK2cGI.js +0 -374
- package/dist/src/ai.studio-nQF_B4DH.cjs +0 -391
- package/dist/src/aimlapi-BeSk5BDV.js +0 -30
- package/dist/src/aimlapi-CzNUhdKu.cjs +0 -29
- package/dist/src/aimlapi-DCkazDPI.js +0 -29
- package/dist/src/aimlapi-WeH5B6K1.js +0 -28
- package/dist/src/anthropicMessages-AcNWTpYO.js +0 -115
- package/dist/src/anthropicMessages-BUtvzbpL.js +0 -114
- package/dist/src/anthropicMessages-DTWhZDCd.js +0 -114
- package/dist/src/anthropicMessages-lGVnSgyx.cjs +0 -114
- package/dist/src/app/assets/index-CyEI5aDN.js +0 -392
- package/dist/src/audio-BE_Duiu1.js +0 -104
- package/dist/src/audio-CiUd00k2.cjs +0 -104
- package/dist/src/audio-Dop6omna.js +0 -105
- package/dist/src/audio-EZNTInr_.js +0 -104
- package/dist/src/authoritativeMarkupInjection-BUqH6hNk.js +0 -78
- package/dist/src/authoritativeMarkupInjection-BmbJdJO0.cjs +0 -78
- package/dist/src/authoritativeMarkupInjection-Cpz12Y3w.js +0 -78
- package/dist/src/authoritativeMarkupInjection-W4XfcH91.js +0 -79
- package/dist/src/aws-B2Jqbnet.js +0 -2433
- package/dist/src/aws-DHtuUIzQ.js +0 -1149
- package/dist/src/aws-DLqXP6wY.js +0 -2433
- package/dist/src/aws-DfduhmwL.cjs +0 -2454
- package/dist/src/base-B9L2Xvpc.js +0 -255
- package/dist/src/base-BTZnIsuH.cjs +0 -305
- package/dist/src/base-BhaWNQPJ.js +0 -462
- package/dist/src/base-Br-G_Xou.js +0 -256
- package/dist/src/base-Bzo8hiUS.cjs +0 -467
- package/dist/src/base-C82fd2jq.js +0 -463
- package/dist/src/base-CRdQwC1W.js +0 -462
- package/dist/src/base-CjGwZNOQ.js +0 -255
- package/dist/src/bedrock-BdMru_ro.js +0 -1300
- package/dist/src/bedrock-CBUmfLX1.cjs +0 -2
- package/dist/src/bedrock-DBDxBaF9.js +0 -2
- package/dist/src/bedrock-DDrECM21.js +0 -1
- package/dist/src/bedrock-DVwkgvRS.js +0 -1
- package/dist/src/bestOfN-AKF5beq4.js +0 -159
- package/dist/src/bestOfN-C1UIVGJV.js +0 -160
- package/dist/src/bestOfN-DlkETbXy.cjs +0 -161
- package/dist/src/bestOfN-XIfEgU-g.js +0 -159
- package/dist/src/cache-BZaVoKaA.js +0 -2
- package/dist/src/cache-CEYhFUsH.js +0 -741
- package/dist/src/cache-Cbw1zH6q.js +0 -782
- package/dist/src/cache-CdaZNb75.js +0 -754
- package/dist/src/cache-DpuIzPxh.cjs +0 -856
- package/dist/src/cerebras-Bpn6FPkJ.js +0 -97
- package/dist/src/cerebras-CqJ0BRCJ.cjs +0 -97
- package/dist/src/cerebras-D4ILa_TY.js +0 -97
- package/dist/src/cerebras-DR8uc7ee.js +0 -98
- package/dist/src/chat-B7PZfaYp.cjs +0 -987
- package/dist/src/chat-BVuSjdEb.js +0 -227
- package/dist/src/chat-BsaZRjXN.js +0 -76
- package/dist/src/chat-C5liqe3Y.js +0 -226
- package/dist/src/chat-C9hyHf5B.js +0 -124
- package/dist/src/chat-CFLalAck.js +0 -226
- package/dist/src/chat-CN8JGrHu.js +0 -76
- package/dist/src/chat-CaHBBpCb.js +0 -123
- package/dist/src/chat-CcbLSghx.js +0 -123
- package/dist/src/chat-CoSN6sr2.js +0 -934
- package/dist/src/chat-CsJRFX8a.cjs +0 -226
- package/dist/src/chat-DFoZLvO_.js +0 -934
- package/dist/src/chat-DiLQvL7r.js +0 -77
- package/dist/src/chat-F6qVSDk3.cjs +0 -123
- package/dist/src/chat-Oq4FaJgI.cjs +0 -76
- package/dist/src/chat-knMfQUKz.js +0 -935
- package/dist/src/claude-agent-sdk-CJkOLpnI.js +0 -999
- package/dist/src/claude-agent-sdk-Ckn76PRD.js +0 -999
- package/dist/src/claude-agent-sdk-DX0gTr11.cjs +0 -1005
- package/dist/src/claude-agent-sdk-LMeikxoR.js +0 -1000
- package/dist/src/client-BFf1r6Z8.js +0 -352
- package/dist/src/client-CdXyDdDi.js +0 -351
- package/dist/src/client-DV8XMu7i.js +0 -351
- package/dist/src/client-iEXtOBKW.cjs +0 -357
- package/dist/src/cloud-B02qcpjf.js +0 -2
- package/dist/src/cloud-BlHP-g1v.cjs +0 -666
- package/dist/src/cloud-C9BI4isF.js +0 -287
- package/dist/src/cloud-CTTLNdvj.js +0 -657
- package/dist/src/cloud-D5X5yQ_d.js +0 -599
- package/dist/src/cloud-QxPkpsHA.js +0 -599
- package/dist/src/cloudflare-ai-DRTRT1fc.js +0 -132
- package/dist/src/cloudflare-ai-DgaXapAV.cjs +0 -133
- package/dist/src/cloudflare-ai-DkuJXijS.js +0 -134
- package/dist/src/cloudflare-ai-wy9QCHku.js +0 -133
- package/dist/src/cloudflare-gateway-9cAN8g29.cjs +0 -276
- package/dist/src/cloudflare-gateway-BVFMD292.js +0 -277
- package/dist/src/cloudflare-gateway-BuUI4p6I.js +0 -276
- package/dist/src/cloudflare-gateway-TlYHizbW.js +0 -276
- package/dist/src/codex-app-server-BPz_feug.cjs +0 -2162
- package/dist/src/codex-app-server-C9noXM9C.js +0 -2155
- package/dist/src/codex-app-server-CEmD-Y5U.js +0 -2
- package/dist/src/codex-app-server-DZolc18r.js +0 -2156
- package/dist/src/codex-app-server-Dd0A37nO.js +0 -2155
- package/dist/src/codex-sdk-BbxjBvBk.cjs +0 -1588
- package/dist/src/codex-sdk-BpXeIs-_.js +0 -1557
- package/dist/src/codex-sdk-BtQp5X0f.js +0 -1552
- package/dist/src/codex-sdk-CH0v9Bk1.js +0 -2
- package/dist/src/codex-sdk-CgrN7JB8.js +0 -1557
- package/dist/src/codex-security-B5fENNUr.js +0 -406
- package/dist/src/codex-security-C6uuJx-y.cjs +0 -411
- package/dist/src/codex-security-CifWtZR_.js +0 -407
- package/dist/src/codex-security-Kg2bOEKy.js +0 -406
- package/dist/src/cometapi-BFpIDLIx.js +0 -55
- package/dist/src/cometapi-C6O2PIrN.js +0 -56
- package/dist/src/cometapi-D5Yupc6F.js +0 -53
- package/dist/src/cometapi-DzgIwJzJ.cjs +0 -55
- package/dist/src/compatible-0jhmVfOp.js +0 -25
- package/dist/src/compatible-DdtwWdJi.cjs +0 -24
- package/dist/src/compatible-Di4nR3sU.js +0 -24
- package/dist/src/compatible-iJKLWdnf.js +0 -24
- package/dist/src/completion-B0_hVUDG.js +0 -119
- package/dist/src/completion-BdI6v0-Q.cjs +0 -123
- package/dist/src/completion-CWcJscwv.js +0 -118
- package/dist/src/conversationScoring-99EOPJRa.js +0 -373
- package/dist/src/conversationScoring-BIq4J0Cq.js +0 -374
- package/dist/src/conversationScoring-DARyeATr.cjs +0 -398
- package/dist/src/conversationScoring-DS48t4Hb.js +0 -373
- package/dist/src/crescendo-CebvXIu4.js +0 -730
- package/dist/src/crescendo-CxrLS0oo.js +0 -732
- package/dist/src/crescendo-DZQXBSkj.js +0 -733
- package/dist/src/crescendo-wjEx68uD.cjs +0 -734
- package/dist/src/custom-BiQ-aUNw.cjs +0 -620
- package/dist/src/custom-ByyB5Z93.js +0 -619
- package/dist/src/custom-C5jOevNb.js +0 -617
- package/dist/src/custom-CCrsCz8o.js +0 -618
- package/dist/src/dataUrl-C9J7wz9H.js +0 -3133
- package/dist/src/dataUrl-CGsBT7UZ.cjs +0 -3187
- package/dist/src/dataUrl-CzKvvWNO.js +0 -2868
- package/dist/src/decisions-BTusXCUx.cjs +0 -514
- package/dist/src/decisions-BYtL9edD.js +0 -515
- package/dist/src/decisions-jYix_gCF.js +0 -515
- package/dist/src/decisions-nxxfHRAN.js +0 -516
- package/dist/src/docker-DDIv4zkg.js +0 -93
- package/dist/src/docker-DVLp_5OG.js +0 -94
- package/dist/src/docker-INm9w0SY.cjs +0 -94
- package/dist/src/docker-sGuoCul_.js +0 -95
- package/dist/src/embedding-CAhqLLrR.js +0 -82
- package/dist/src/embedding-D94GAsti.cjs +0 -86
- package/dist/src/embedding-zb4MAzQy.js +0 -81
- package/dist/src/envoy-BMPszwEa.cjs +0 -60
- package/dist/src/envoy-Bp-s39_8.js +0 -60
- package/dist/src/envoy-BswDt-Ou.js +0 -61
- package/dist/src/envoy-rVbYcV80.js +0 -60
- package/dist/src/eval-B1-XfrC7.js +0 -2
- package/dist/src/eval-pC4qCvLw.js +0 -1920
- package/dist/src/evalResult-CzjCb5bd.js +0 -1300
- package/dist/src/evaluator-CNq2K7J_.js +0 -10578
- package/dist/src/evaluator-LtfxdxEu.js +0 -2
- package/dist/src/evaluatorHelpers-C5J0NYpQ.js +0 -601
- package/dist/src/evaluatorHelpers-DcdCglCs.cjs +0 -622
- package/dist/src/evaluatorHelpers-Rvb88W6B.js +0 -600
- package/dist/src/fetch-5HtnNhHg.js +0 -1556
- package/dist/src/fetch-BCwjoIFd.js +0 -1559
- package/dist/src/fetch-BhfUnKen.js +0 -1280
- package/dist/src/fetch-C08w4vVl.js +0 -9
- package/dist/src/fetch-DBxy8Tlb.cjs +0 -14
- package/dist/src/fetch-DYte6oQX.cjs +0 -1853
- package/dist/src/fetch-ElpfcAVG.js +0 -10
- package/dist/src/fetch-lM1OIaSt.js +0 -2
- package/dist/src/functionCallbackUtils-BCAdWfHO.js +0 -246
- package/dist/src/functionCallbackUtils-BYq9qOhG.js +0 -245
- package/dist/src/functionCallbackUtils-ByWkmVud.cjs +0 -262
- package/dist/src/functionCallbackUtils-D_C_JaFd.js +0 -245
- package/dist/src/gemini-image-CAlwaJhZ.js +0 -471
- package/dist/src/gemini-image-DJoVoOnZ.cjs +0 -471
- package/dist/src/gemini-image-Waio-9Xx.js +0 -472
- package/dist/src/gemini-image-vbY6Ftbf.js +0 -471
- package/dist/src/goat-BWbtyY9c.js +0 -512
- package/dist/src/goat-CXzzeCOQ.cjs +0 -515
- package/dist/src/goat-CfgvzSgZ.js +0 -514
- package/dist/src/goat-DLeoIHBp.js +0 -515
- package/dist/src/goblin-CmfCfwqk.cjs +0 -22
- package/dist/src/goblin-CtcaORRH.js +0 -22
- package/dist/src/goblin-DF6DDhM-.js +0 -22
- package/dist/src/goblin-Nai0xxBV.js +0 -23
- package/dist/src/google-ClpkLtKL.cjs +0 -142
- package/dist/src/google-CrFgKu9k.js +0 -143
- package/dist/src/google-Dj_EIBqS.js +0 -142
- package/dist/src/google-e4-vARv6.js +0 -142
- package/dist/src/graders-BN1IIa71.js +0 -15470
- package/dist/src/graders-C5sCfSP5.js +0 -2
- package/dist/src/hydra-B7RfXVBK.js +0 -2
- package/dist/src/hydra-C1cFN-Jg.js +0 -613
- package/dist/src/hydra-CK6Uc1ia.js +0 -612
- package/dist/src/hydra-Dg65rAP_.js +0 -610
- package/dist/src/hydra-Fqx8l_Xu.cjs +0 -612
- package/dist/src/image-BdlUDAtp.js +0 -163
- package/dist/src/image-C-KwEhwt.cjs +0 -163
- package/dist/src/image-C76fp6NK.cjs +0 -209
- package/dist/src/image-D75ehv3D.js +0 -482
- package/dist/src/image-DKtc5v5W.js +0 -209
- package/dist/src/image-DT_xOkJY.js +0 -163
- package/dist/src/image-DVpdllEE.js +0 -209
- package/dist/src/image-DXZ8AzMf.js +0 -481
- package/dist/src/image-DZl_1mvy.js +0 -164
- package/dist/src/image-DcTD7Ak0.cjs +0 -504
- package/dist/src/image-ndxEvBKB.js +0 -210
- package/dist/src/indirectWebPwn-70WA5x_q.js +0 -435
- package/dist/src/indirectWebPwn-BRVyd3qV.cjs +0 -463
- package/dist/src/indirectWebPwn-BsJVsu0q.js +0 -285
- package/dist/src/indirectWebPwn-Cw6yJu0i.js +0 -285
- package/dist/src/indirectWebPwn-D-PhJzo4.js +0 -286
- package/dist/src/indirectWebPwn-DRPHt2I8.js +0 -434
- package/dist/src/indirectWebPwn-DXtfFhd9.cjs +0 -285
- package/dist/src/indirectWebPwn-DvpNouzc.js +0 -434
- package/dist/src/interactions-5ixfW7Cl.js +0 -850
- package/dist/src/interactions-BOUzfmg1.cjs +0 -850
- package/dist/src/interactions-CSSXcGNc.js +0 -851
- package/dist/src/interactions-WB_sgkN0.js +0 -850
- package/dist/src/iterative-BShRNqjC.cjs +0 -549
- package/dist/src/iterative-BcW_ELtB.js +0 -548
- package/dist/src/iterative-BpHbHz8B.js +0 -547
- package/dist/src/iterative-YcchP12d.js +0 -545
- package/dist/src/iterativeImage-BMFE5Hl7.cjs +0 -419
- package/dist/src/iterativeImage-CkSaYLMY.js +0 -417
- package/dist/src/iterativeImage-DAEUlQX_.js +0 -418
- package/dist/src/iterativeImage-DeUSkEnj.js +0 -417
- package/dist/src/iterativeMeta-DbdaKdEO.cjs +0 -439
- package/dist/src/iterativeMeta-DcF9JWEK.js +0 -437
- package/dist/src/iterativeMeta-DkfPqWIC.js +0 -439
- package/dist/src/iterativeMeta-DkrpnVSs.js +0 -440
- package/dist/src/iterativeTree-CEMY1FhE.cjs +0 -879
- package/dist/src/iterativeTree-Cv5W4tX9.js +0 -877
- package/dist/src/iterativeTree-DGvomd3M.js +0 -876
- package/dist/src/iterativeTree-KbtC2Mkz.js +0 -878
- package/dist/src/knowledgeBase-AYRXn4wO.js +0 -163
- package/dist/src/knowledgeBase-BNqQfx01.js +0 -163
- package/dist/src/knowledgeBase-C0IaCCVs.js +0 -164
- package/dist/src/knowledgeBase-C2uj6wK4.cjs +0 -163
- package/dist/src/litellm-CN1ScAwZ.js +0 -147
- package/dist/src/litellm-CTjwiKSa.js +0 -146
- package/dist/src/litellm-Cd4Nj5ue.js +0 -145
- package/dist/src/litellm-DMqLpEBg.cjs +0 -146
- package/dist/src/live-Bt50-InO.cjs +0 -891
- package/dist/src/live-Bu2ujadG.js +0 -2
- package/dist/src/live-C3M52i3p.js +0 -891
- package/dist/src/live-CRfHMO3B.js +0 -892
- package/dist/src/live-iCIpO14m.js +0 -891
- package/dist/src/luma-ray-B8eHC4c8.cjs +0 -309
- package/dist/src/luma-ray-BIvkPw0s.js +0 -309
- package/dist/src/luma-ray-ChsiBxMA.js +0 -309
- package/dist/src/luma-ray-DAvKrInz.js +0 -310
- package/dist/src/mantleChat-5YeEXAZI.js +0 -154
- package/dist/src/mantleChat-D--mW3hl.js +0 -154
- package/dist/src/mantleChat-DHP3siNg.cjs +0 -154
- package/dist/src/mantleChat-wBfAX46o.js +0 -155
- package/dist/src/mcp-49jlEcTQ.js +0 -85
- package/dist/src/mcp-__PIz81I.js +0 -84
- package/dist/src/memoryPoisoning-Chxge74M.js +0 -111
- package/dist/src/memoryPoisoning-QH6NCUVE.js +0 -112
- package/dist/src/memoryPoisoning-tCxZt8Nz.js +0 -111
- package/dist/src/memoryPoisoning-ymrRItRR.cjs +0 -111
- package/dist/src/messages-BE_eZ7NA.js +0 -1127
- package/dist/src/messages-Bo638ShI.js +0 -1127
- package/dist/src/messages-BzR0-NQ1.js +0 -1128
- package/dist/src/messages-Cbg5Pfd0.cjs +0 -1161
- package/dist/src/mischievousUser-BtED2Z5Z.cjs +0 -51
- package/dist/src/mischievousUser-KqSOjrDJ.js +0 -51
- package/dist/src/mischievousUser-YMD41YNZ.js +0 -51
- package/dist/src/mischievousUser-zBxlNdCu.js +0 -52
- package/dist/src/modelslab-B8gZxtfE.js +0 -171
- package/dist/src/modelslab-C7bmVpTB.js +0 -170
- package/dist/src/modelslab-CCeQCTZg.js +0 -170
- package/dist/src/modelslab-CvBJGv7y.cjs +0 -170
- package/dist/src/nova-reel-BOEWsEKA.cjs +0 -272
- package/dist/src/nova-reel-BtyBXq5M.js +0 -273
- package/dist/src/nova-reel-C1eF3-EE.js +0 -272
- package/dist/src/nova-reel-YhgrxEKT.js +0 -272
- package/dist/src/nova-sonic-BaERP20w.js +0 -374
- package/dist/src/nova-sonic-CxgM3O-P.cjs +0 -374
- package/dist/src/nova-sonic-DJS22fC3.js +0 -375
- package/dist/src/nova-sonic-DwvwHyRX.js +0 -374
- package/dist/src/novita-D1l2tG0O.js +0 -116
- package/dist/src/novita-D84VM3v0.cjs +0 -115
- package/dist/src/novita-DGInvwfR.js +0 -115
- package/dist/src/novita-vXHkXhTg.js +0 -114
- package/dist/src/nscale-CU0Ldr00.js +0 -209
- package/dist/src/nscale-ClfKlQeM.cjs +0 -209
- package/dist/src/nscale-DxaVnF0x.js +0 -210
- package/dist/src/nscale-_poVxIrO.js +0 -207
- package/dist/src/openaiResponses-CJGsneNj.js +0 -317
- package/dist/src/openaiResponses-CKSl5Sdg.js +0 -319
- package/dist/src/openaiResponses-DC8rzFUc.js +0 -318
- package/dist/src/openaiResponses-rMHIA3dV.cjs +0 -318
- package/dist/src/openclaw-CCbvsNFW.cjs +0 -1344
- package/dist/src/openclaw-CZw1lCDZ.js +0 -1336
- package/dist/src/openclaw-ChFLgC-a.js +0 -1338
- package/dist/src/openclaw-DrPMoRrH.js +0 -1337
- package/dist/src/opencode-sdk-2EfIDhFp.cjs +0 -1018
- package/dist/src/opencode-sdk-CHw3F845.js +0 -1013
- package/dist/src/opencode-sdk-D0EGGxvy.js +0 -1012
- package/dist/src/opencode-sdk-jSS7NI2Q.js +0 -1012
- package/dist/src/openinterpreter-CY622xHC.js +0 -431
- package/dist/src/openinterpreter-DATyhuhP.js +0 -432
- package/dist/src/openinterpreter-DDUMyIf1.js +0 -431
- package/dist/src/openinterpreter-s5Tz6Lid.cjs +0 -435
- package/dist/src/pricing-C498twd2.js +0 -976
- package/dist/src/pricing-DBd_bMIh.cjs +0 -999
- package/dist/src/pricing-ISrMCskv.js +0 -976
- package/dist/src/promptfoo-6p0WG2ih.js +0 -286
- package/dist/src/promptfoo-BQRfgNs_.js +0 -285
- package/dist/src/promptfoo-Bqo88z20.cjs +0 -341
- package/dist/src/promptfoo-Cq5AVI3I.js +0 -286
- package/dist/src/providers-ATbeMX5E.cjs +0 -24180
- package/dist/src/providers-B7F1FiNI.js +0 -2
- package/dist/src/providers-DazIw8mI.js +0 -24039
- package/dist/src/providers-QvGlNAD0.js +0 -24437
- package/dist/src/quiverai-BPlNcLkV.cjs +0 -325
- package/dist/src/quiverai-By99-YmJ.js +0 -325
- package/dist/src/quiverai-CmmByDqo.js +0 -325
- package/dist/src/quiverai-HZkoJJqw.js +0 -326
- package/dist/src/registry-BLTnTfjT.cjs +0 -131
- package/dist/src/registry-BMC-COHT.js +0 -132
- package/dist/src/registry-C8UZtmBp.js +0 -131
- package/dist/src/registry-CfwEUYXG.js +0 -131
- package/dist/src/remoteGeneration-Bgg6kC5V.js +0 -370
- package/dist/src/remoteGeneration-CKJFE960.cjs +0 -482
- package/dist/src/remoteGeneration-DaxwJDKv.js +0 -370
- package/dist/src/remoteGeneration-vHMGVKr1.js +0 -371
- package/dist/src/responses-BvnyIHrq.js +0 -1099
- package/dist/src/responses-CERAgvf-.js +0 -1098
- package/dist/src/responses-CwgjeuOg.cjs +0 -1109
- package/dist/src/runtimeTransform-3RF9Avyg.cjs +0 -161
- package/dist/src/runtimeTransform-C51Z5j4H.js +0 -156
- package/dist/src/runtimeTransform-CKE_Iyv8.js +0 -157
- package/dist/src/runtimeTransform-Cs11xJpj.js +0 -156
- package/dist/src/sagemaker-BJ4dOCxE.js +0 -713
- package/dist/src/sagemaker-Bt3JUAWq.js +0 -714
- package/dist/src/sagemaker-QVKyLgLF.cjs +0 -716
- package/dist/src/sagemaker-l3IB8iTs.js +0 -713
- package/dist/src/scanner-DGjIiTZ1.js +0 -1971
- package/dist/src/server-BNaLZH6u.js +0 -63
- package/dist/src/server-CBMuQul3.js +0 -189
- package/dist/src/server-CMKKIzKv.cjs +0 -74
- package/dist/src/server-CN2IS9Cs.js +0 -155
- package/dist/src/server-ua4cqbjC.js +0 -2
- package/dist/src/session-CQIjq_HZ.cjs +0 -69
- package/dist/src/session-FTlg8sEu.js +0 -62
- package/dist/src/session-GBQXtPWp.js +0 -63
- package/dist/src/session-_0-rTdwY.js +0 -62
- package/dist/src/shared-BtuO-OAM.js +0 -1927
- package/dist/src/shared-Ci78wTZX.js +0 -1928
- package/dist/src/shared-Dd3rimdM.js +0 -1929
- package/dist/src/shared-KAjFxRre.cjs +0 -2185
- package/dist/src/simulatedUser--OjW6xCx.js +0 -247
- package/dist/src/simulatedUser-D0-slRye.cjs +0 -252
- package/dist/src/simulatedUser-D1Kh_cwA.js +0 -248
- package/dist/src/slack-BX0FUevO.js +0 -144
- package/dist/src/slack-CcwXFiJE.cjs +0 -144
- package/dist/src/slack-CnfqKfXp.js +0 -145
- package/dist/src/strategies-2ntcK4tN.js +0 -2248
- package/dist/src/strategies-DZIW7Zcs.js +0 -2385
- package/dist/src/strategies-YRDF2j-N.cjs +0 -2420
- package/dist/src/telemetry-CL8CVA4p.js +0 -205
- package/dist/src/telemetry-Cix90qqz.js +0 -201
- package/dist/src/telemetry-CkmlqTjV.js +0 -205
- package/dist/src/telemetry-DQH7l5bb.js +0 -2
- package/dist/src/telemetry-DUCXc1-r.cjs +0 -216
- package/dist/src/togetherai-3yUt5Ck7.js +0 -49
- package/dist/src/togetherai-BJUByLwt.cjs +0 -49
- package/dist/src/togetherai-Bh6KZkmd.js +0 -50
- package/dist/src/togetherai-DDl9yk3b.js +0 -48
- package/dist/src/traceContext-BiJVppjT.cjs +0 -1032
- package/dist/src/traceContext-CKKluULP.js +0 -1016
- package/dist/src/traceContext-D3Sk8fO-.js +0 -1015
- package/dist/src/transcription-BoVQKIVz.js +0 -175
- package/dist/src/transcription-Bs5u8780.js +0 -175
- package/dist/src/transcription-Dl654d1p.cjs +0 -178
- package/dist/src/transcription-VEvhM1Fu.js +0 -176
- package/dist/src/transform-BAbkGpvl.js +0 -125
- package/dist/src/transform-C_j4hUH8.js +0 -126
- package/dist/src/transform-Da86hs4M.cjs +0 -154
- package/dist/src/transform-_i1raQNo.js +0 -125
- package/dist/src/typesafe-CKXBeh82.cjs +0 -253
- package/dist/src/typesafe-D3VaGeNm.js +0 -253
- package/dist/src/typesafe-DYM-SFNH.js +0 -253
- package/dist/src/typesafe-LXmSbw8F.js +0 -254
- package/dist/src/util-41rb0XHX.js +0 -743
- package/dist/src/util-B0WlHYFM.js +0 -2049
- package/dist/src/util-BD1R_PsR.js +0 -1770
- package/dist/src/util-BLbM_ItC.js +0 -395
- package/dist/src/util-C72P0iIa.js +0 -352
- package/dist/src/util-C7MpGlJ6.cjs +0 -2305
- package/dist/src/util-CCtqagKc.js +0 -286
- package/dist/src/util-CM2Ec_uY.js +0 -2051
- package/dist/src/util-CSSAWO8v.js +0 -351
- package/dist/src/util-CkQc-fx3.cjs +0 -934
- package/dist/src/util-DcOlFTFa.cjs +0 -478
- package/dist/src/util-DluGTGVB.js +0 -395
- package/dist/src/util-PfQ60chz.js +0 -743
- package/dist/src/util-f14-CPij.js +0 -396
- package/dist/src/util-l9pXX3Eu.cjs +0 -357
- package/dist/src/util-o_BnlYqS.js +0 -2050
- package/dist/src/vertex-B_-gQgzc.js +0 -2
- package/dist/src/vertex-CU4gqoYa.js +0 -854
- package/dist/src/vertex-DZ42UHb3.js +0 -848
- package/dist/src/vertex-DsYQJA0T.cjs +0 -871
- package/dist/src/vertexLive-BvIaHKGB.js +0 -55
- package/dist/src/vertexLive-CTULqNx7.js +0 -56
- package/dist/src/vertexLive-DjJ_YQBe.cjs +0 -55
- package/dist/src/vertexLive-STUGcteB.js +0 -55
- package/dist/src/video-CAQGmtEV.js +0 -703
- package/dist/src/video-_FSTWlwX.js +0 -702
- package/dist/src/video-aBPJPWdt.js +0 -702
- package/dist/src/video-gDjkWv5m.cjs +0 -706
|
@@ -0,0 +1,3133 @@
|
|
|
1
|
+
import { B as safeJsonStringify, D as stripProviderPromptSelectors, H as loadYaml, K as getEnvBool, N as isApiProvider, O as stripTestProviderPromptSelectors, P as isProviderOptions, S as sanitizeRuntimeOptions, W as refreshConfigDirectoryPathFromEnv, at as loadEnvFiles, b as sanitizeConfigForOutput, h as mapTestProviderRefs, j as isProviderTypeMap, k as GRADING_PROVIDER_TYPE_KEYS, l as REDACTED, o as logger, p as isSecretField, rt as parseEnvBool, w as sanitizeUrl, x as sanitizeObject } from "./logger-Gff7RMNF.js";
|
|
2
|
+
import { W as TERMINAL_MAX_WIDTH, X as VERSION, t as fetchWithProxy } from "./fetch-B5KxZuKu.js";
|
|
3
|
+
import { t as invariant } from "./invariant-CHP0zrix.js";
|
|
4
|
+
import { t as getCurrentTimestamp } from "./time-Db5rO0GY.js";
|
|
5
|
+
import { n as PromptfooAttributes } from "./genaiTracer-CuSjHi4t.js";
|
|
6
|
+
import { n as sha256 } from "./createHash-D7vud8fe.js";
|
|
7
|
+
import { r as getDirectory } from "./esm-D2FLmzG-.js";
|
|
8
|
+
import { o as getNunjucksEngine } from "./render-DuOHcYot.js";
|
|
9
|
+
import { i as maybeLoadConfigFromExternalFile } from "./file-Di0ypTgN.js";
|
|
10
|
+
import { E as isResultFailureReason, Nt as getDb, Pt as getDbSignalPath, T as ResultFailureReason, a as evalResultsTable, b as OutputFileExtension } from "./tables-D7gZdWrS.js";
|
|
11
|
+
import { d as createEmptyTokenUsage, o as accumulateGradingTokenUsage, s as accumulateResponseTokenUsage } from "./tokenUsageUtils-BfzeNdYY.js";
|
|
12
|
+
import "./blobs-DiN8G72v.js";
|
|
13
|
+
import { a as collectBlobHashes, n as isBlobStorageEnabled, o as extractBlobHashesFromValue, t as extractAndStoreBinaryData } from "./extractor-NglB5koy.js";
|
|
14
|
+
import { t as isMissingPackageImportError } from "./packageImportErrors-rgCkzu81.js";
|
|
15
|
+
import fs, * as fs$2 from "fs";
|
|
16
|
+
import path, * as path$1 from "path";
|
|
17
|
+
import * as os$1 from "os";
|
|
18
|
+
import * as yaml from "js-yaml";
|
|
19
|
+
import deepEqual from "fast-deep-equal";
|
|
20
|
+
import { isDeepStrictEqual } from "node:util";
|
|
21
|
+
import { randomUUID } from "crypto";
|
|
22
|
+
import dedent from "dedent";
|
|
23
|
+
import * as fsPromises from "fs/promises";
|
|
24
|
+
import { and, eq, gte, inArray, lt, ne, sql } from "drizzle-orm";
|
|
25
|
+
import { LRUCache } from "lru-cache";
|
|
26
|
+
import { XMLBuilder } from "fast-xml-parser";
|
|
27
|
+
import { stringify } from "csv-stringify/sync";
|
|
28
|
+
//#region src/providers/constants.ts
|
|
29
|
+
const FILE_METADATA_KEY = "_promptfooFileMetadata";
|
|
30
|
+
/**
|
|
31
|
+
* Identifier for manual user ratings in componentResults.
|
|
32
|
+
* Used to distinguish human ratings from automated assertions.
|
|
33
|
+
*/
|
|
34
|
+
const HUMAN_ASSERTION_TYPE = "human";
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/util/debounce.ts
|
|
37
|
+
/** Coalesce calls at the trailing edge and allow pending work to be cancelled on cleanup. */
|
|
38
|
+
function debounce(callback, wait) {
|
|
39
|
+
let timeout;
|
|
40
|
+
function debounced(...args) {
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
timeout = setTimeout(() => {
|
|
43
|
+
timeout = void 0;
|
|
44
|
+
callback.apply(this, args);
|
|
45
|
+
}, wait);
|
|
46
|
+
}
|
|
47
|
+
debounced.clear = () => {
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
timeout = void 0;
|
|
50
|
+
};
|
|
51
|
+
return debounced;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/database/signal.ts
|
|
55
|
+
const SIGNAL_WATCHER_DEBOUNCE_MS = 250;
|
|
56
|
+
/**
|
|
57
|
+
* The legacy `evalId:timestamp` text format can't be told apart from a stray short token, so
|
|
58
|
+
* {@link readLegacySignalEvalId} only recovers eval ids at least this long. Generated ids
|
|
59
|
+
* (`eval-<rand>-<iso>`) always clear it; a short caller-provided id (e.g. an imported `demo`) is
|
|
60
|
+
* written as JSON instead, which stores the id explicitly and survives the round trip.
|
|
61
|
+
*/
|
|
62
|
+
const MIN_LEGACY_EVAL_ID_LENGTH = 9;
|
|
63
|
+
/** Whether {@link readLegacySignalEvalId} can recover this id from the legacy text format. */
|
|
64
|
+
function isLegacyRecoverableEvalId(evalId) {
|
|
65
|
+
return evalId.length >= MIN_LEGACY_EVAL_ID_LENGTH;
|
|
66
|
+
}
|
|
67
|
+
/** Returns the string entries of an unknown value, or undefined if it is not an array. */
|
|
68
|
+
function toStringEvalIds(value) {
|
|
69
|
+
return Array.isArray(value) ? value.filter((evalId) => typeof evalId === "string") : void 0;
|
|
70
|
+
}
|
|
71
|
+
function writeSignalFile(content) {
|
|
72
|
+
const filePath = getDbSignalPath();
|
|
73
|
+
try {
|
|
74
|
+
fs.writeFileSync(filePath, content);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger.warn(`Failed to write database signal file: ${err}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Updates the signal file with the current timestamp and optional eval ID.
|
|
81
|
+
* This is used to notify clients that there are new data available.
|
|
82
|
+
*
|
|
83
|
+
* If another signal is still pending in the watcher's debounce window, this folds it into a
|
|
84
|
+
* combined JSON payload so no refresh is lost — scoped updates, a pending unscoped "refresh
|
|
85
|
+
* latest", and pending deletes all survive coalescing. When nothing is pending it keeps the
|
|
86
|
+
* compact legacy `evalId:timestamp` text.
|
|
87
|
+
* This read-modify-write is best-effort within a single process; concurrent writes from separate
|
|
88
|
+
* processes can still race the unlocked file, in which case a dropped signal self-heals on the
|
|
89
|
+
* next one.
|
|
90
|
+
* @param evalId - Optional eval ID that triggered the update
|
|
91
|
+
*/
|
|
92
|
+
function updateSignalFile(evalId) {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const nowIso = new Date(now).toISOString();
|
|
95
|
+
const pending = readPendingSignal(now);
|
|
96
|
+
const pendingUpdateIds = pending ? updateEvalIds(pending) : [];
|
|
97
|
+
const pendingHasDelete = pending ? hasDeleteComponent(pending) : false;
|
|
98
|
+
const pendingHasUnscopedUpdate = pending ? hasUnscopedUpdate(pending) : false;
|
|
99
|
+
const updatedEvalIds = [.../* @__PURE__ */ new Set([...pendingUpdateIds, ...evalId ? [evalId] : []])];
|
|
100
|
+
const refreshLatest = evalId === void 0 || pendingHasUnscopedUpdate;
|
|
101
|
+
const needsCoalesce = pendingHasDelete || pendingUpdateIds.some((id) => id !== evalId) || refreshLatest && updatedEvalIds.length > 0;
|
|
102
|
+
const evalIdNeedsJson = evalId !== void 0 && !isLegacyRecoverableEvalId(evalId);
|
|
103
|
+
if (needsCoalesce || evalIdNeedsJson) {
|
|
104
|
+
writeSignalFile(JSON.stringify({
|
|
105
|
+
type: "update",
|
|
106
|
+
evalId,
|
|
107
|
+
updatedEvalIds: updatedEvalIds.length > 0 ? updatedEvalIds : void 0,
|
|
108
|
+
deletedEvalIds: pendingHasDelete ? pending?.deletedEvalIds ?? [] : void 0,
|
|
109
|
+
refreshLatest: refreshLatest ? true : void 0,
|
|
110
|
+
timestamp: nowIso
|
|
111
|
+
}));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
writeSignalFile(evalId ? `${evalId}:${nowIso}` : nowIso);
|
|
115
|
+
}
|
|
116
|
+
/** True when the signal carries an update component (a scoped or unscoped eval refresh). */
|
|
117
|
+
function hasUpdateComponent(signal) {
|
|
118
|
+
return signal.type === "update" || signal.evalId !== void 0 || (signal.updatedEvalIds?.length ?? 0) > 0 || signal.refreshLatest === true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* True when the signal asks to refresh the latest eval (an unscoped update component): the
|
|
122
|
+
* `refreshLatest` marker, or a bare update with no scoped ids (e.g. a legacy bare timestamp). The
|
|
123
|
+
* watcher emits {@link Eval.latest} for these, so a root `/eval` view follows new evals even when a
|
|
124
|
+
* coalesced signal also carries scoped updates or a delete.
|
|
125
|
+
*/
|
|
126
|
+
function hasUnscopedUpdate(signal) {
|
|
127
|
+
return signal.refreshLatest === true || hasUpdateComponent(signal) && updateEvalIds(signal).length === 0;
|
|
128
|
+
}
|
|
129
|
+
/** True when the signal carries a delete component (specific ids or "all evals deleted"). */
|
|
130
|
+
function hasDeleteComponent(signal) {
|
|
131
|
+
return signal.type === "delete" || signal.deletedEvalIds !== void 0;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* True when a delete component means "all evals were deleted" rather than a specific id list.
|
|
135
|
+
* "All" is encoded as `undefined` by direct delete callers and as `[]` when a pending delete-all
|
|
136
|
+
* is folded into a JSON update, so both count. Only meaningful for a signal that actually carries
|
|
137
|
+
* a delete component (see {@link hasDeleteComponent}); an update-only signal has no ids either.
|
|
138
|
+
*/
|
|
139
|
+
function isAllEvalsDeleted(deletedEvalIds) {
|
|
140
|
+
return deletedEvalIds === void 0 || deletedEvalIds.length === 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The distinct scoped eval ids an update signal refreshes, newest last. Empty for an unscoped
|
|
144
|
+
* update (which means "refresh to the latest eval"). Prefers the accumulated `updatedEvalIds`
|
|
145
|
+
* set and falls back to the single `evalId` for legacy/simple signals.
|
|
146
|
+
*/
|
|
147
|
+
function updateEvalIds(signal) {
|
|
148
|
+
if (signal.updatedEvalIds && signal.updatedEvalIds.length > 0) return signal.updatedEvalIds;
|
|
149
|
+
return signal.evalId ? [signal.evalId] : [];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Parses signal-file content as a combined JSON payload, returning the classified signal and
|
|
153
|
+
* its timestamp (used for the debounce-window check), or null if it is not a JSON signal.
|
|
154
|
+
*/
|
|
155
|
+
function parseSignalJson(content) {
|
|
156
|
+
try {
|
|
157
|
+
const parsed = JSON.parse(content);
|
|
158
|
+
if (parsed !== null && typeof parsed === "object" && "type" in parsed && (parsed.type === "delete" || parsed.type === "update")) {
|
|
159
|
+
const evalId = "evalId" in parsed && typeof parsed.evalId === "string" ? parsed.evalId : void 0;
|
|
160
|
+
const updatedEvalIds = "updatedEvalIds" in parsed ? toStringEvalIds(parsed.updatedEvalIds) : void 0;
|
|
161
|
+
const deletedEvalIds = "deletedEvalIds" in parsed ? toStringEvalIds(parsed.deletedEvalIds) : void 0;
|
|
162
|
+
const timestamp = "timestamp" in parsed && typeof parsed.timestamp === "string" ? parsed.timestamp : void 0;
|
|
163
|
+
const refreshLatest = "refreshLatest" in parsed && parsed.refreshLatest === true ? true : void 0;
|
|
164
|
+
return {
|
|
165
|
+
signal: {
|
|
166
|
+
type: parsed.type,
|
|
167
|
+
evalId,
|
|
168
|
+
updatedEvalIds,
|
|
169
|
+
deletedEvalIds,
|
|
170
|
+
refreshLatest
|
|
171
|
+
},
|
|
172
|
+
timestamp
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
} catch {}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Reads the signal file only if it was written within the watcher's debounce window, so a
|
|
180
|
+
* writer can fold a not-yet-observed signal into its own write instead of clobbering it.
|
|
181
|
+
* Returns null when the file is stale (outside the window), unreadable, or unparseable.
|
|
182
|
+
*/
|
|
183
|
+
function readPendingSignal(now) {
|
|
184
|
+
const withinWindow = (timestamp) => {
|
|
185
|
+
if (timestamp === void 0) return false;
|
|
186
|
+
const elapsed = now - Date.parse(timestamp);
|
|
187
|
+
return Number.isFinite(elapsed) && elapsed >= 0 && elapsed <= SIGNAL_WATCHER_DEBOUNCE_MS;
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
const content = fs.readFileSync(getDbSignalPath(), "utf8").trim();
|
|
191
|
+
const json = parseSignalJson(content);
|
|
192
|
+
if (json) return withinWindow(json.timestamp) ? json.signal : null;
|
|
193
|
+
const legacyTimestamp = content.match(/(\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)$/)?.[1];
|
|
194
|
+
if (!withinWindow(legacyTimestamp)) return null;
|
|
195
|
+
return {
|
|
196
|
+
type: "update",
|
|
197
|
+
evalId: readLegacySignalEvalId(content)
|
|
198
|
+
};
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Signals that one or more evals were deleted. Deletions use a JSON payload so the watcher can
|
|
205
|
+
* tell clients exactly which evals to drop (whereas a plain update keeps the legacy
|
|
206
|
+
* `evalId:timestamp` text format unless it has to fold in a pending delete). A delete in the
|
|
207
|
+
* debounce window also carries forward a pending update — scoped ids or an unscoped "refresh
|
|
208
|
+
* latest" — so neither refresh is lost (same-process best-effort, like {@link updateSignalFile};
|
|
209
|
+
* cross-process writes can still race).
|
|
210
|
+
* Pre-PR view servers don't understand this JSON and read it back as "no eval id", falling back
|
|
211
|
+
* to broadcasting the latest eval — a benign degradation, so the view server and CLI should be
|
|
212
|
+
* on matching versions for delete-aware refreshes.
|
|
213
|
+
*/
|
|
214
|
+
function updateSignalFileForDeletedEvals(deletedEvalIds) {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
const pending = readPendingSignal(now);
|
|
217
|
+
const pendingDelete = pending && hasDeleteComponent(pending) ? pending : null;
|
|
218
|
+
const mergedDeletedEvalIds = pendingDelete ? isAllEvalsDeleted(pendingDelete.deletedEvalIds) || isAllEvalsDeleted(deletedEvalIds) ? void 0 : [.../* @__PURE__ */ new Set([...pendingDelete.deletedEvalIds ?? [], ...deletedEvalIds ?? []])] : deletedEvalIds;
|
|
219
|
+
const carriedUpdateIds = pending && hasUpdateComponent(pending) ? updateEvalIds(pending) : [];
|
|
220
|
+
const carriedRefreshLatest = pending ? hasUnscopedUpdate(pending) : false;
|
|
221
|
+
writeSignalFile(JSON.stringify({
|
|
222
|
+
type: "delete",
|
|
223
|
+
deletedEvalIds: mergedDeletedEvalIds,
|
|
224
|
+
evalId: carriedUpdateIds[carriedUpdateIds.length - 1],
|
|
225
|
+
updatedEvalIds: carriedUpdateIds.length > 0 ? carriedUpdateIds : void 0,
|
|
226
|
+
refreshLatest: carriedRefreshLatest ? true : void 0,
|
|
227
|
+
timestamp: new Date(now).toISOString()
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
function readLegacySignalEvalId(content) {
|
|
231
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(content)) return;
|
|
232
|
+
const evalId = content.match(/^(.*):\d{4}-\d{2}-\d{2}T/)?.[1];
|
|
233
|
+
return evalId && isLegacyRecoverableEvalId(evalId) ? evalId : void 0;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Reads and classifies the signal file into a {@link DatabaseSignal}. It understands the
|
|
237
|
+
* legacy `evalId:timestamp` (or bare timestamp) text written by {@link updateSignalFile} and
|
|
238
|
+
* the JSON payload written by the delete/combined writers. A JSON payload may carry BOTH an
|
|
239
|
+
* update component (`updatedEvalIds` — possibly several ids when scoped updates coalesced) and a
|
|
240
|
+
* delete component (`deletedEvalIds`) when mutations were coalesced inside the watcher's debounce
|
|
241
|
+
* window. Malformed/half-written content or a read error degrades to an unscoped `{type:'update'}`.
|
|
242
|
+
*/
|
|
243
|
+
function readSignalFile() {
|
|
244
|
+
const filePath = getDbSignalPath();
|
|
245
|
+
try {
|
|
246
|
+
const content = fs.readFileSync(filePath, "utf8").trim();
|
|
247
|
+
const json = parseSignalJson(content);
|
|
248
|
+
if (json) return json.signal;
|
|
249
|
+
return {
|
|
250
|
+
type: "update",
|
|
251
|
+
evalId: readLegacySignalEvalId(content)
|
|
252
|
+
};
|
|
253
|
+
} catch {
|
|
254
|
+
return { type: "update" };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Ensures the signal file exists, creating it if necessary.
|
|
259
|
+
*/
|
|
260
|
+
function ensureSignalFile() {
|
|
261
|
+
const filePath = getDbSignalPath();
|
|
262
|
+
if (!fs.existsSync(filePath)) {
|
|
263
|
+
logger.debug(`Creating signal file at ${filePath}`);
|
|
264
|
+
fs.writeFileSync(filePath, (/* @__PURE__ */ new Date()).toISOString());
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Sets up a watcher on the signal file and calls the callback when it changes.
|
|
269
|
+
* @param onChange - Callback function that is called when the signal file changes
|
|
270
|
+
* @returns The watcher instance
|
|
271
|
+
*/
|
|
272
|
+
function setupSignalWatcher(onChange) {
|
|
273
|
+
const filePath = getDbSignalPath();
|
|
274
|
+
logger.debug(`Setting up file watcher on ${filePath}`);
|
|
275
|
+
ensureSignalFile();
|
|
276
|
+
try {
|
|
277
|
+
const watcher = fs.watch(filePath);
|
|
278
|
+
const handleChange = debounce(onChange, SIGNAL_WATCHER_DEBOUNCE_MS);
|
|
279
|
+
watcher.on("change", handleChange);
|
|
280
|
+
watcher.on("close", handleChange.clear);
|
|
281
|
+
watcher.on("error", (error) => {
|
|
282
|
+
logger.warn(`File watcher error: ${error}`);
|
|
283
|
+
});
|
|
284
|
+
return watcher;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.warn(`Failed to set up file watcher: ${error}`);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/models/prompt.ts
|
|
292
|
+
/**
|
|
293
|
+
* Generates a unique identifier for a prompt based on its properties.
|
|
294
|
+
*
|
|
295
|
+
* Priority order:
|
|
296
|
+
* 1. If label is truthy, hash the label
|
|
297
|
+
* 2. If id is truthy, hash the id
|
|
298
|
+
* 3. Otherwise, hash the raw content (stringified if object)
|
|
299
|
+
*
|
|
300
|
+
* @param prompt - The prompt object to generate an ID for
|
|
301
|
+
* @returns A SHA-256 hash string
|
|
302
|
+
*/
|
|
303
|
+
function generateIdFromPrompt(prompt) {
|
|
304
|
+
if (prompt.label) return sha256(prompt.label);
|
|
305
|
+
if (prompt.id) return sha256(prompt.id);
|
|
306
|
+
return sha256(typeof prompt.raw === "object" ? JSON.stringify(prompt.raw) : prompt.raw);
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/prompts/constants.ts
|
|
310
|
+
const VALID_FILE_EXTENSIONS = [
|
|
311
|
+
".cjs",
|
|
312
|
+
".cts",
|
|
313
|
+
".j2",
|
|
314
|
+
".js",
|
|
315
|
+
".json",
|
|
316
|
+
".jsonl",
|
|
317
|
+
".md",
|
|
318
|
+
".mjs",
|
|
319
|
+
".mts",
|
|
320
|
+
".py",
|
|
321
|
+
".ts",
|
|
322
|
+
".txt",
|
|
323
|
+
".yml",
|
|
324
|
+
".yaml"
|
|
325
|
+
];
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/prompts/utils.ts
|
|
328
|
+
/**
|
|
329
|
+
* Determines if a string is a valid file path.
|
|
330
|
+
* @param str - The string to check.
|
|
331
|
+
* @returns True if the string is a valid file path, false otherwise.
|
|
332
|
+
*/
|
|
333
|
+
function maybeFilePath(str) {
|
|
334
|
+
if (typeof str !== "string") throw new Error(`Invalid input: ${JSON.stringify(str)}`);
|
|
335
|
+
if ([
|
|
336
|
+
"\n",
|
|
337
|
+
"portkey://",
|
|
338
|
+
"langfuse://",
|
|
339
|
+
"helicone://"
|
|
340
|
+
].some((substring) => str.includes(substring))) return false;
|
|
341
|
+
return str.startsWith("file://") || VALID_FILE_EXTENSIONS.some((ext) => {
|
|
342
|
+
const tokens = str.split(":");
|
|
343
|
+
const pathOrFunctionToken = tokens[tokens.length - 1];
|
|
344
|
+
const pathToken = tokens[tokens.length - 2];
|
|
345
|
+
return pathOrFunctionToken?.endsWith(ext) || pathToken?.endsWith(ext);
|
|
346
|
+
}) || str.charAt(str.length - 3) === "." || str.charAt(str.length - 4) === "." || str.includes("*") || str.includes("/") || str.includes("\\");
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Normalizes the input prompt to an array of prompts, rejecting invalid and empty inputs.
|
|
350
|
+
* @param promptPathOrGlobs - The input prompt.
|
|
351
|
+
* @returns The normalized prompts.
|
|
352
|
+
* @throws If the input is invalid or empty.
|
|
353
|
+
*/
|
|
354
|
+
function normalizeInput(promptPathOrGlobs) {
|
|
355
|
+
if (!promptPathOrGlobs || (typeof promptPathOrGlobs === "string" || Array.isArray(promptPathOrGlobs)) && promptPathOrGlobs.length === 0) throw new Error(`Invalid input prompt: ${JSON.stringify(promptPathOrGlobs)}`);
|
|
356
|
+
if (typeof promptPathOrGlobs === "string") return [{ raw: promptPathOrGlobs }];
|
|
357
|
+
if (Array.isArray(promptPathOrGlobs)) return promptPathOrGlobs.map((promptPathOrGlob, _index) => {
|
|
358
|
+
if (typeof promptPathOrGlob === "string") return { raw: promptPathOrGlob };
|
|
359
|
+
return {
|
|
360
|
+
raw: promptPathOrGlob.raw || promptPathOrGlob.id,
|
|
361
|
+
...promptPathOrGlob
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
if (typeof promptPathOrGlobs === "object" && Object.keys(promptPathOrGlobs).length) return Object.entries(promptPathOrGlobs).map(([raw, key]) => ({
|
|
365
|
+
label: key,
|
|
366
|
+
raw
|
|
367
|
+
}));
|
|
368
|
+
throw new Error(`Invalid input prompt: ${JSON.stringify(promptPathOrGlobs)}`);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Generates a hash identifier for a prompt.
|
|
372
|
+
* This is an alias for generateIdFromPrompt for backward compatibility.
|
|
373
|
+
*
|
|
374
|
+
* @param prompt - The prompt to hash
|
|
375
|
+
* @returns A SHA-256 hash string
|
|
376
|
+
*/
|
|
377
|
+
function hashPrompt(prompt) {
|
|
378
|
+
return generateIdFromPrompt(prompt);
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/util/standaloneEvalCache.ts
|
|
382
|
+
const standaloneEvalCache = new LRUCache({
|
|
383
|
+
ttl: 72e5,
|
|
384
|
+
max: 2e3
|
|
385
|
+
});
|
|
386
|
+
function getStandaloneEvalCacheKey({ limit = 100, tag, description } = {}) {
|
|
387
|
+
return `standalone_evals_${limit}_${tag?.key}_${tag?.value}_${description}`;
|
|
388
|
+
}
|
|
389
|
+
function getCachedStandaloneEvals(cacheKey) {
|
|
390
|
+
return standaloneEvalCache.get(cacheKey);
|
|
391
|
+
}
|
|
392
|
+
function setCachedStandaloneEvals(cacheKey, evals) {
|
|
393
|
+
standaloneEvalCache.set(cacheKey, evals);
|
|
394
|
+
}
|
|
395
|
+
function clearStandaloneEvalCache() {
|
|
396
|
+
standaloneEvalCache.clear();
|
|
397
|
+
}
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/models/evalPerformance.ts
|
|
400
|
+
const countCache = /* @__PURE__ */ new Map();
|
|
401
|
+
const CACHE_TTL = 3e5;
|
|
402
|
+
async function getResultCount(evalId, distinct) {
|
|
403
|
+
const cacheKey = `${distinct ? "distinct" : "total"}:${evalId}`;
|
|
404
|
+
const cached = countCache.get(cacheKey);
|
|
405
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
406
|
+
logger.debug(`Using cached ${distinct ? "distinct count" : "total row count"} for eval ${evalId}: ${cached.count}`);
|
|
407
|
+
return cached.count;
|
|
408
|
+
}
|
|
409
|
+
const db = await getDb();
|
|
410
|
+
const start = Date.now();
|
|
411
|
+
const result = await db.select({ count: distinct ? sql`COUNT(DISTINCT test_idx)` : sql`COUNT(*)` }).from(evalResultsTable).where(sql`eval_id = ${evalId}`).all();
|
|
412
|
+
const count = Number(result[0]?.count ?? 0);
|
|
413
|
+
const duration = Date.now() - start;
|
|
414
|
+
logger.debug(`${distinct ? "Distinct count" : "Total row count"} query for eval ${evalId}: ${count} in ${duration}ms`);
|
|
415
|
+
countCache.set(cacheKey, {
|
|
416
|
+
count,
|
|
417
|
+
timestamp: Date.now()
|
|
418
|
+
});
|
|
419
|
+
return count;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get the count of distinct test indices for an eval.
|
|
423
|
+
* This represents the number of unique test cases (rows in the UI table).
|
|
424
|
+
*
|
|
425
|
+
* Use getTotalResultRowCount() if you need the total number of result rows
|
|
426
|
+
* (which may be higher when there are multiple prompts/providers per test case).
|
|
427
|
+
*/
|
|
428
|
+
function getCachedResultsCount(evalId) {
|
|
429
|
+
return getResultCount(evalId, true);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get the total count of all result rows for an eval.
|
|
433
|
+
* This counts every result row in the database, including multiple results
|
|
434
|
+
* per test case (e.g., when using multiple prompts or providers).
|
|
435
|
+
*
|
|
436
|
+
* Use this for progress tracking when iterating over all results (e.g., sharing).
|
|
437
|
+
*/
|
|
438
|
+
function getTotalResultRowCount(evalId) {
|
|
439
|
+
return getResultCount(evalId, false);
|
|
440
|
+
}
|
|
441
|
+
function clearCountCache(evalId) {
|
|
442
|
+
if (evalId) {
|
|
443
|
+
countCache.delete(`distinct:${evalId}`);
|
|
444
|
+
countCache.delete(`total:${evalId}`);
|
|
445
|
+
} else countCache.clear();
|
|
446
|
+
}
|
|
447
|
+
async function queryTestIndicesOptimized(evalId, opts) {
|
|
448
|
+
const db = await getDb();
|
|
449
|
+
const offset = opts.offset ?? 0;
|
|
450
|
+
const limit = opts.limit ?? 50;
|
|
451
|
+
const mode = opts.filterMode ?? "all";
|
|
452
|
+
let baseQuery = sql`eval_id = ${evalId}`;
|
|
453
|
+
if (mode === "errors") baseQuery = sql`${baseQuery} AND failure_reason = ${2}`;
|
|
454
|
+
else if (mode === "failures") baseQuery = sql`${baseQuery} AND success = 0 AND failure_reason != ${2}`;
|
|
455
|
+
else if (mode === "passes") baseQuery = sql`${baseQuery} AND success = 1`;
|
|
456
|
+
else if (mode === "highlights") baseQuery = sql`${baseQuery} AND json_extract(grading_result, '$.comment') LIKE '!highlight%'`;
|
|
457
|
+
else if (mode === "user-rated") baseQuery = sql`${baseQuery} AND EXISTS (
|
|
458
|
+
SELECT 1
|
|
459
|
+
FROM json_each(grading_result, '$.componentResults')
|
|
460
|
+
WHERE json_extract(value, '$.assertion.type') = ${HUMAN_ASSERTION_TYPE}
|
|
461
|
+
)`;
|
|
462
|
+
let searchCondition = sql`1=1`;
|
|
463
|
+
if (opts.searchQuery && opts.searchQuery.trim() !== "" && !opts.filters?.length) searchCondition = sql`response LIKE ${`%${opts.searchQuery}%`}`;
|
|
464
|
+
const whereClause = sql`${baseQuery} AND ${searchCondition}`;
|
|
465
|
+
const countStart = Date.now();
|
|
466
|
+
const countQuery = sql`
|
|
467
|
+
SELECT COUNT(DISTINCT test_idx) as count
|
|
468
|
+
FROM ${evalResultsTable}
|
|
469
|
+
WHERE ${whereClause}
|
|
470
|
+
`;
|
|
471
|
+
const countResult = await db.all(countQuery);
|
|
472
|
+
const filteredCount = Number(countResult[0]?.count ?? 0);
|
|
473
|
+
logger.debug(`Optimized count query took ${Date.now() - countStart}ms`);
|
|
474
|
+
const idxStart = Date.now();
|
|
475
|
+
const idxQuery = sql`
|
|
476
|
+
SELECT DISTINCT test_idx
|
|
477
|
+
FROM ${evalResultsTable}
|
|
478
|
+
WHERE ${whereClause}
|
|
479
|
+
ORDER BY test_idx
|
|
480
|
+
LIMIT ${limit}
|
|
481
|
+
OFFSET ${offset}
|
|
482
|
+
`;
|
|
483
|
+
const testIndices = (await db.all(idxQuery)).map((row) => row.test_idx);
|
|
484
|
+
logger.debug(`Optimized index query took ${Date.now() - idxStart}ms`);
|
|
485
|
+
return {
|
|
486
|
+
testIndices,
|
|
487
|
+
filteredCount
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/models/evalMutation.ts
|
|
492
|
+
/**
|
|
493
|
+
* Drops this process's cached eval aggregates so the next read re-queries the DB.
|
|
494
|
+
* Always clears the standalone-eval LRU (keyed by filter combos, no per-eval granularity)
|
|
495
|
+
* and the count cache for `evalId` (or every eval's counts when `evalId` is omitted).
|
|
496
|
+
* Use this for in-process cache coherence only — it does NOT notify the view server.
|
|
497
|
+
*/
|
|
498
|
+
function invalidateEvaluationCache(evalId) {
|
|
499
|
+
clearStandaloneEvalCache();
|
|
500
|
+
clearCountCache(evalId);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Like {@link invalidateEvaluationCache} but for several evals at once: clears the standalone-eval
|
|
504
|
+
* LRU once (it has no per-eval granularity, so repeating it per id is wasteful) and the count
|
|
505
|
+
* cache for each id.
|
|
506
|
+
*/
|
|
507
|
+
function invalidateEvaluationCaches(evalIds) {
|
|
508
|
+
clearStandaloneEvalCache();
|
|
509
|
+
for (const id of evalIds) clearCountCache(id);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Call after creating or mutating an eval's data (results, prompts, ratings). Invalidates
|
|
513
|
+
* this process's caches and writes a scoped update signal so a running `promptfoo view`
|
|
514
|
+
* server refreshes clients viewing this eval (and the root route, which follows the latest).
|
|
515
|
+
*/
|
|
516
|
+
function notifyEvaluationChanged(evalId) {
|
|
517
|
+
invalidateEvaluationCache(evalId);
|
|
518
|
+
updateSignalFile(evalId);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Call after deleting one or more evals. Invalidates this process's caches and writes a
|
|
522
|
+
* delete signal so the view server can navigate clients off the removed evals. Pass no
|
|
523
|
+
* argument (or omit ids) for "all evals deleted".
|
|
524
|
+
*/
|
|
525
|
+
function notifyEvaluationsDeleted(evalIds) {
|
|
526
|
+
if (evalIds) invalidateEvaluationCaches(evalIds);
|
|
527
|
+
else invalidateEvaluationCache();
|
|
528
|
+
updateSignalFileForDeletedEvals(evalIds);
|
|
529
|
+
}
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/models/evalResult.ts
|
|
532
|
+
function sanitizeProviderConfig(config) {
|
|
533
|
+
return sanitizeObject(JSON.parse(safeJsonStringify(config)), {
|
|
534
|
+
context: "provider config",
|
|
535
|
+
sanitizeUrls: true,
|
|
536
|
+
maxDepth: Number.POSITIVE_INFINITY
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
function stripMediaReferences(value) {
|
|
540
|
+
if (extractBlobHashesFromValue(value).length > 0 || typeof value === "string" && /^data:[^,]*,/i.test(value)) return "[output stripped]";
|
|
541
|
+
if (Array.isArray(value)) return value.map(stripMediaReferences);
|
|
542
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, stripMediaReferences(item)]));
|
|
543
|
+
return value;
|
|
544
|
+
}
|
|
545
|
+
function projectOutputMetadata(metadata, stripOutput, responseMetadata, testMetadata) {
|
|
546
|
+
if (!stripOutput || !metadata || !responseMetadata || typeof metadata !== "object") return metadata;
|
|
547
|
+
return Object.fromEntries(Object.entries(metadata).flatMap(([key, value]) => {
|
|
548
|
+
if (!Object.prototype.hasOwnProperty.call(responseMetadata, key) || testMetadata && isDeepStrictEqual(value, testMetadata[key])) return [[key, value]];
|
|
549
|
+
return key === "audio" || key === "blobUris" ? [] : [[key, stripMediaReferences(sanitizeForDb(value))]];
|
|
550
|
+
}));
|
|
551
|
+
}
|
|
552
|
+
function projectProviderResponse(response, options) {
|
|
553
|
+
if (!response) return response;
|
|
554
|
+
if (!options.stripMetadata && !options.stripOutput) return response;
|
|
555
|
+
const projectedResponse = options.stripMetadata ? (({ metadata: _metadata, ...rest }) => rest)(response) : { ...response };
|
|
556
|
+
if (options.stripOutput) {
|
|
557
|
+
projectedResponse.output = "[output stripped]";
|
|
558
|
+
delete projectedResponse.raw;
|
|
559
|
+
delete projectedResponse.providerTransformedOutput;
|
|
560
|
+
delete projectedResponse.conversationOutput;
|
|
561
|
+
delete projectedResponse.audio;
|
|
562
|
+
delete projectedResponse.video;
|
|
563
|
+
delete projectedResponse.images;
|
|
564
|
+
if (projectedResponse.metadata) projectedResponse.metadata = projectOutputMetadata(projectedResponse.metadata, true, projectedResponse.metadata);
|
|
565
|
+
}
|
|
566
|
+
return projectedResponse;
|
|
567
|
+
}
|
|
568
|
+
function projectPrompt(prompt, stripPromptText) {
|
|
569
|
+
return stripPromptText ? {
|
|
570
|
+
...prompt,
|
|
571
|
+
raw: "[prompt stripped]",
|
|
572
|
+
template: void 0,
|
|
573
|
+
...prompt.config?.provider && { config: {
|
|
574
|
+
...prompt.config,
|
|
575
|
+
provider: stripProviderPromptSelectors(prompt.config.provider)
|
|
576
|
+
} }
|
|
577
|
+
} : prompt;
|
|
578
|
+
}
|
|
579
|
+
function projectTracesForOutput(traces, { shouldStripMetadata, shouldStripPromptText, shouldStripResponseOutput, shouldStripTestVars }) {
|
|
580
|
+
if (!shouldStripMetadata && !shouldStripPromptText && !shouldStripResponseOutput && !shouldStripTestVars) return traces;
|
|
581
|
+
return traces.map((trace) => {
|
|
582
|
+
let projectedTrace = trace;
|
|
583
|
+
if (shouldStripMetadata) {
|
|
584
|
+
const { metadata: _metadata, ...traceWithoutMetadata } = trace;
|
|
585
|
+
projectedTrace = traceWithoutMetadata;
|
|
586
|
+
} else if (shouldStripTestVars && trace.metadata && "vars" in trace.metadata) {
|
|
587
|
+
const { metadata: traceMetadata, ...traceWithoutMetadata } = trace;
|
|
588
|
+
const { vars: _vars, ...metadata } = traceMetadata;
|
|
589
|
+
projectedTrace = {
|
|
590
|
+
...traceWithoutMetadata,
|
|
591
|
+
...Object.keys(metadata).length > 0 && { metadata }
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
if (!shouldStripPromptText && !shouldStripResponseOutput) return projectedTrace;
|
|
595
|
+
return {
|
|
596
|
+
...projectedTrace,
|
|
597
|
+
spans: projectedTrace.spans.map((span) => {
|
|
598
|
+
if (!span.attributes) return span;
|
|
599
|
+
const projectedAttributes = { ...span.attributes };
|
|
600
|
+
if (shouldStripPromptText) delete projectedAttributes[PromptfooAttributes.REQUEST_BODY];
|
|
601
|
+
if (shouldStripResponseOutput) delete projectedAttributes[PromptfooAttributes.RESPONSE_BODY];
|
|
602
|
+
const { attributes: _attributes, ...projectedSpan } = span;
|
|
603
|
+
return {
|
|
604
|
+
...projectedSpan,
|
|
605
|
+
...Object.keys(projectedAttributes).length > 0 && { attributes: projectedAttributes }
|
|
606
|
+
};
|
|
607
|
+
})
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
function projectTestCase(testCase, options) {
|
|
612
|
+
if (!options.stripMetadata && !options.stripVars && !options.stripOutput && !options.stripPromptText) return testCase;
|
|
613
|
+
const projectedTestCase = options.stripMetadata ? (({ metadata: _metadata, ...rest }) => rest)(testCase) : { ...testCase };
|
|
614
|
+
if (options.stripVars) projectedTestCase.vars = void 0;
|
|
615
|
+
if (options.stripOutput) delete projectedTestCase.providerOutput;
|
|
616
|
+
if (options.stripMetadata && testCase.metadata?.__promptfoo?.remote === true) projectedTestCase.metadata = { __promptfoo: { remote: true } };
|
|
617
|
+
return options.stripPromptText ? stripTestProviderPromptSelectors(projectedTestCase) : projectedTestCase;
|
|
618
|
+
}
|
|
619
|
+
/** Map only grading assertion providers and component results. */
|
|
620
|
+
function mapGradingResultProviderRefs(gradingResult, mapProvider) {
|
|
621
|
+
const visited = /* @__PURE__ */ new WeakMap();
|
|
622
|
+
const project = (value) => {
|
|
623
|
+
const result = asRecord(value);
|
|
624
|
+
if (!result) return value;
|
|
625
|
+
const previous = visited.get(result);
|
|
626
|
+
if (previous) return previous;
|
|
627
|
+
const projected = { ...result };
|
|
628
|
+
visited.set(result, projected);
|
|
629
|
+
if (result.assertion) projected.assertion = mapTestProviderRefs(result.assertion, mapProvider);
|
|
630
|
+
if (Array.isArray(result.componentResults)) projected.componentResults = result.componentResults.map(project);
|
|
631
|
+
return projected;
|
|
632
|
+
};
|
|
633
|
+
return project(gradingResult);
|
|
634
|
+
}
|
|
635
|
+
/** Project assertion providers only, preserving grading details and unrelated metadata. */
|
|
636
|
+
function projectGradingResult(gradingResult, stripPromptText) {
|
|
637
|
+
return stripPromptText ? mapGradingResultProviderRefs(gradingResult, stripProviderPromptSelectors) : gradingResult;
|
|
638
|
+
}
|
|
639
|
+
function sanitizeProvider(provider) {
|
|
640
|
+
try {
|
|
641
|
+
if (isApiProvider(provider)) return {
|
|
642
|
+
id: provider.id(),
|
|
643
|
+
label: provider.label,
|
|
644
|
+
...provider.config && { config: sanitizeProviderConfig(provider.config) }
|
|
645
|
+
};
|
|
646
|
+
if (isProviderOptions(provider)) return {
|
|
647
|
+
id: provider.id,
|
|
648
|
+
label: provider.label,
|
|
649
|
+
...provider.config && { config: sanitizeProviderConfig(provider.config) }
|
|
650
|
+
};
|
|
651
|
+
if (typeof provider === "object" && provider) {
|
|
652
|
+
const providerObj = provider;
|
|
653
|
+
return {
|
|
654
|
+
id: typeof providerObj.id === "function" ? providerObj.id() : providerObj.id,
|
|
655
|
+
label: providerObj.label,
|
|
656
|
+
...providerObj.config && { config: sanitizeProviderConfig(providerObj.config) }
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
} catch {}
|
|
660
|
+
return JSON.parse(safeJsonStringify(provider));
|
|
661
|
+
}
|
|
662
|
+
/** Snapshot live provider references for replay without retaining runtime client state. */
|
|
663
|
+
function toSerializableProviderRef(provider) {
|
|
664
|
+
if (isApiProvider(provider)) return {
|
|
665
|
+
...sanitizeProvider(provider),
|
|
666
|
+
...sanitizeObject({
|
|
667
|
+
transform: typeof provider.transform === "string" ? provider.transform : void 0,
|
|
668
|
+
delay: provider.delay,
|
|
669
|
+
inputs: provider.inputs
|
|
670
|
+
}, {
|
|
671
|
+
context: "provider options",
|
|
672
|
+
sanitizeUrls: true,
|
|
673
|
+
maxDepth: Number.POSITIVE_INFINITY
|
|
674
|
+
}),
|
|
675
|
+
...provider.prompts && { prompts: [...provider.prompts] }
|
|
676
|
+
};
|
|
677
|
+
if (Array.isArray(provider)) return provider.map(toSerializableProviderRef);
|
|
678
|
+
if (isProviderTypeMap(provider)) {
|
|
679
|
+
let serialized;
|
|
680
|
+
for (const type of GRADING_PROVIDER_TYPE_KEYS) if (isApiProvider(provider[type])) {
|
|
681
|
+
serialized ??= { ...provider };
|
|
682
|
+
serialized[type] = toSerializableProviderRef(provider[type]);
|
|
683
|
+
}
|
|
684
|
+
return serialized ?? provider;
|
|
685
|
+
}
|
|
686
|
+
return provider;
|
|
687
|
+
}
|
|
688
|
+
/** Snapshot live grading references before generic result serialization invokes provider toJSON. */
|
|
689
|
+
function serializeResultProviderRefs(result) {
|
|
690
|
+
const record = result;
|
|
691
|
+
const serializeProvider = (provider) => {
|
|
692
|
+
if (!isApiProvider(provider) && !isProviderTypeMap(provider)) return provider;
|
|
693
|
+
return sanitizeObject(toSerializableProviderRef(provider), {
|
|
694
|
+
context: "grading provider",
|
|
695
|
+
sanitizeUrls: true,
|
|
696
|
+
maxDepth: Number.POSITIVE_INFINITY,
|
|
697
|
+
throwOnError: true
|
|
698
|
+
});
|
|
699
|
+
};
|
|
700
|
+
const projected = { ...record };
|
|
701
|
+
if (record.testCase) projected.testCase = mapTestProviderRefs(record.testCase, serializeProvider);
|
|
702
|
+
const prompt = asRecord(record.prompt);
|
|
703
|
+
const config = asRecord(prompt?.config);
|
|
704
|
+
if (config?.provider !== void 0) projected.prompt = {
|
|
705
|
+
...prompt,
|
|
706
|
+
config: {
|
|
707
|
+
...config,
|
|
708
|
+
provider: serializeProvider(config.provider)
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
if (record.gradingResult) projected.gradingResult = mapGradingResultProviderRefs(record.gradingResult, serializeProvider);
|
|
712
|
+
return projected;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Sanitize an object for database storage by removing circular references
|
|
716
|
+
* and non-serializable values (functions, Timeout objects, etc.).
|
|
717
|
+
* Uses safeJsonStringify which handles circular references gracefully.
|
|
718
|
+
*
|
|
719
|
+
* This prevents "Converting circular structure to JSON" errors that can occur
|
|
720
|
+
* when Node.js Timeout objects or other non-serializable data leaks into results.
|
|
721
|
+
* See: https://github.com/promptfoo/promptfoo/issues/7266
|
|
722
|
+
*/
|
|
723
|
+
function sanitizeForDb(obj) {
|
|
724
|
+
if (obj === null || obj === void 0) return obj;
|
|
725
|
+
try {
|
|
726
|
+
const serialized = safeJsonStringify(obj);
|
|
727
|
+
if (serialized === void 0) {
|
|
728
|
+
logger.debug("sanitizeForDb: Failed to serialize object, using fallback", {
|
|
729
|
+
valueType: typeof obj,
|
|
730
|
+
isArray: Array.isArray(obj)
|
|
731
|
+
});
|
|
732
|
+
return Array.isArray(obj) ? [] : null;
|
|
733
|
+
}
|
|
734
|
+
return JSON.parse(serialized);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
logger.debug("sanitizeForDb: Parse error, using fallback", { error });
|
|
737
|
+
return Array.isArray(obj) ? [] : null;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Sanitize a per-test-case field for persistence: strips circular refs,
|
|
742
|
+
* collapses class instances (e.g. live SDK clients that leaked in via
|
|
743
|
+
* `defaultTest.options.provider`), and redacts credential fields (`apiKey`,
|
|
744
|
+
* `token`, etc.) at any depth. Use this for any slot that can carry a provider
|
|
745
|
+
* config — notably `testCase.options.provider` and `prompt.config.provider`,
|
|
746
|
+
* where the resolved runtime provider (with its Anthropic / Bedrock SDK
|
|
747
|
+
* client) flows in from the evaluator. Without this, credentials configured on
|
|
748
|
+
* the judge provider end up in the Eval results both in the DB and in the
|
|
749
|
+
* polling response served by `/api/eval/job/:id`.
|
|
750
|
+
*/
|
|
751
|
+
function sanitizeForDbWithSecrets(obj) {
|
|
752
|
+
if (obj === null || obj === void 0) return obj;
|
|
753
|
+
return sanitizeObject(obj, {
|
|
754
|
+
context: "evalResult field",
|
|
755
|
+
maxDepth: Number.POSITIVE_INFINITY
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
const SENSITIVE_RESPONSE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
759
|
+
"authorization",
|
|
760
|
+
"proxy-authorization",
|
|
761
|
+
"cookie",
|
|
762
|
+
"set-cookie",
|
|
763
|
+
"openai-organization",
|
|
764
|
+
"openai-project",
|
|
765
|
+
"openai-version",
|
|
766
|
+
"x-request-id",
|
|
767
|
+
"x-amzn-requestid",
|
|
768
|
+
"x-amzn-trace-id",
|
|
769
|
+
"x-amz-security-token",
|
|
770
|
+
"x-amz-cf-id",
|
|
771
|
+
"x-azure-ref",
|
|
772
|
+
"x-correlation-id",
|
|
773
|
+
"x-trace-id",
|
|
774
|
+
"cf-ray",
|
|
775
|
+
"cf-cache-status",
|
|
776
|
+
"x-openai-proxy-wasm",
|
|
777
|
+
"via"
|
|
778
|
+
]);
|
|
779
|
+
const SENSITIVE_RESPONSE_HEADER_PREFIXES = ["x-ratelimit-"];
|
|
780
|
+
function isSensitiveResponseHeader(headerName) {
|
|
781
|
+
const normalized = headerName.toLowerCase();
|
|
782
|
+
if (SENSITIVE_RESPONSE_HEADER_NAMES.has(normalized)) return true;
|
|
783
|
+
return SENSITIVE_RESPONSE_HEADER_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
784
|
+
}
|
|
785
|
+
function isSensitiveRequestHeader(headerName) {
|
|
786
|
+
return isSensitiveResponseHeader(headerName) || isSecretField(headerName);
|
|
787
|
+
}
|
|
788
|
+
function redactSensitiveHeaders(headers, sourceHeaders = headers, isSensitiveHeader = isSensitiveResponseHeader) {
|
|
789
|
+
let mutated = false;
|
|
790
|
+
const next = {};
|
|
791
|
+
for (const [key, value] of Object.entries(headers)) if (Object.prototype.hasOwnProperty.call(sourceHeaders, key) && isDeepStrictEqual(sourceHeaders[key], value) && isSensitiveHeader(key)) {
|
|
792
|
+
next[key] = REDACTED;
|
|
793
|
+
mutated = true;
|
|
794
|
+
} else next[key] = value;
|
|
795
|
+
return mutated ? next : null;
|
|
796
|
+
}
|
|
797
|
+
function redactHttpHeadersOnMetadata(metadata, options) {
|
|
798
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return metadata;
|
|
799
|
+
const m = metadata;
|
|
800
|
+
let nextMetadata;
|
|
801
|
+
const legacyHeaders = m.headers;
|
|
802
|
+
const legacyHeadersSource = options?.redactLegacyHeaders ? legacyHeaders : (options?.legacyHeadersSource)?.headers;
|
|
803
|
+
if (legacyHeaders && typeof legacyHeaders === "object" && !Array.isArray(legacyHeaders) && legacyHeadersSource && typeof legacyHeadersSource === "object" && !Array.isArray(legacyHeadersSource)) {
|
|
804
|
+
const redacted = redactSensitiveHeaders(legacyHeaders, legacyHeadersSource, isSensitiveRequestHeader);
|
|
805
|
+
if (redacted) nextMetadata = {
|
|
806
|
+
...m,
|
|
807
|
+
headers: redacted
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
const http = m.http;
|
|
811
|
+
if (!http || typeof http !== "object" || Array.isArray(http)) return nextMetadata ?? metadata;
|
|
812
|
+
const httpRecord = http;
|
|
813
|
+
let nextHttp;
|
|
814
|
+
for (const slot of ["headers", "requestHeaders"]) {
|
|
815
|
+
const slotValue = httpRecord[slot];
|
|
816
|
+
if (slotValue && typeof slotValue === "object" && !Array.isArray(slotValue)) {
|
|
817
|
+
const redacted = slot === "requestHeaders" ? redactSensitiveHeaders(slotValue, slotValue, isSensitiveRequestHeader) : redactSensitiveHeaders(slotValue);
|
|
818
|
+
if (redacted) {
|
|
819
|
+
nextHttp ??= { ...httpRecord };
|
|
820
|
+
nextHttp[slot] = redacted;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
if (!nextHttp) return nextMetadata ?? metadata;
|
|
825
|
+
nextMetadata ??= { ...m };
|
|
826
|
+
nextMetadata.http = nextHttp;
|
|
827
|
+
return nextMetadata;
|
|
828
|
+
}
|
|
829
|
+
function redactHttpHeadersOnGradingResult(gradingResult) {
|
|
830
|
+
if (!gradingResult || typeof gradingResult !== "object" || Array.isArray(gradingResult)) return gradingResult;
|
|
831
|
+
const gr = gradingResult;
|
|
832
|
+
let mutated = false;
|
|
833
|
+
const next = { ...gr };
|
|
834
|
+
if (gr.metadata !== void 0) {
|
|
835
|
+
const redacted = redactHttpHeadersOnMetadata(gr.metadata);
|
|
836
|
+
if (redacted !== gr.metadata) {
|
|
837
|
+
next.metadata = redacted;
|
|
838
|
+
mutated = true;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (Array.isArray(gr.componentResults)) {
|
|
842
|
+
let componentMutated = false;
|
|
843
|
+
const nextComponents = gr.componentResults.map((component) => {
|
|
844
|
+
const redacted = redactHttpHeadersOnGradingResult(component);
|
|
845
|
+
if (redacted !== component) componentMutated = true;
|
|
846
|
+
return redacted;
|
|
847
|
+
});
|
|
848
|
+
if (componentMutated) {
|
|
849
|
+
next.componentResults = nextComponents;
|
|
850
|
+
mutated = true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return mutated ? next : gradingResult;
|
|
854
|
+
}
|
|
855
|
+
function sanitizeResponseForDb(response) {
|
|
856
|
+
if (!response) return response;
|
|
857
|
+
const redactedMetadata = redactHttpHeadersOnMetadata(response.metadata, { redactLegacyHeaders: true });
|
|
858
|
+
if (redactedMetadata === response.metadata) return response;
|
|
859
|
+
return {
|
|
860
|
+
...response,
|
|
861
|
+
metadata: redactedMetadata
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function sanitizeMetadataForDb(metadata, responseMetadata) {
|
|
865
|
+
return redactHttpHeadersOnMetadata(metadata, { legacyHeadersSource: sanitizeForDb(responseMetadata) });
|
|
866
|
+
}
|
|
867
|
+
function sanitizeGradingResultAssertions(gradingResult) {
|
|
868
|
+
if (!gradingResult || typeof gradingResult !== "object" || Array.isArray(gradingResult)) return gradingResult;
|
|
869
|
+
const gr = gradingResult;
|
|
870
|
+
const assertion = asRecord(gr.assertion);
|
|
871
|
+
const sanitizedAssertion = assertion && { ...assertion };
|
|
872
|
+
if (sanitizedAssertion) {
|
|
873
|
+
for (const field of ["provider", "config"]) if (Object.prototype.hasOwnProperty.call(sanitizedAssertion, field)) sanitizedAssertion[field] = sanitizeObject(sanitizedAssertion[field], {
|
|
874
|
+
context: "grading assertion",
|
|
875
|
+
maxDepth: Number.POSITIVE_INFINITY,
|
|
876
|
+
sanitizeUrls: true,
|
|
877
|
+
throwOnError: true
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
...gr,
|
|
882
|
+
...sanitizedAssertion && { assertion: sanitizedAssertion },
|
|
883
|
+
...Array.isArray(gr.componentResults) && { componentResults: gr.componentResults.map(sanitizeGradingResultAssertions) }
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function sanitizeGradingResultForDb(gradingResult) {
|
|
887
|
+
return redactHttpHeadersOnGradingResult(sanitizeGradingResultAssertions(gradingResult));
|
|
888
|
+
}
|
|
889
|
+
const PROMPTFOO_METADATA_KEY = "__promptfoo";
|
|
890
|
+
const TRACE_LINKAGE_KEY = "traceLinkage";
|
|
891
|
+
const REPEAT_LINKAGE_KEY = "repeatLinkage";
|
|
892
|
+
function asRecord(value) {
|
|
893
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
894
|
+
}
|
|
895
|
+
function persistTraceMetadata(metadata, traceId, evaluationId) {
|
|
896
|
+
if (!traceId && !evaluationId) return stripTraceLinkageFromMetadata(metadata);
|
|
897
|
+
const metadataRecord = metadata ?? {};
|
|
898
|
+
const promptfooMetadata = asRecord(metadataRecord[PROMPTFOO_METADATA_KEY]);
|
|
899
|
+
if (metadataRecord["__promptfoo"] !== void 0 && promptfooMetadata === void 0) logger.warn(`[EvalResult] Overwriting non-object metadata.${PROMPTFOO_METADATA_KEY} with internal trace linkage; the key is reserved for promptfoo internals.`);
|
|
900
|
+
if (promptfooMetadata && TRACE_LINKAGE_KEY in promptfooMetadata) logger.warn(`[EvalResult] Overwriting metadata.${PROMPTFOO_METADATA_KEY}.${TRACE_LINKAGE_KEY} with internal trace linkage; the path is reserved for promptfoo internals.`);
|
|
901
|
+
return {
|
|
902
|
+
...metadataRecord,
|
|
903
|
+
[PROMPTFOO_METADATA_KEY]: {
|
|
904
|
+
...promptfooMetadata ?? {},
|
|
905
|
+
[TRACE_LINKAGE_KEY]: {
|
|
906
|
+
traceId,
|
|
907
|
+
evaluationId
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
function stripTraceLinkageFromMetadata(metadata) {
|
|
913
|
+
const metadataRecord = asRecord(metadata);
|
|
914
|
+
const promptfooMetadata = asRecord(metadataRecord?.[PROMPTFOO_METADATA_KEY]);
|
|
915
|
+
if (!metadataRecord || !promptfooMetadata || !(TRACE_LINKAGE_KEY in promptfooMetadata)) return metadata;
|
|
916
|
+
const { [TRACE_LINKAGE_KEY]: _traceLinkage, ...remainingPromptfooMetadata } = promptfooMetadata;
|
|
917
|
+
const strippedMetadata = { ...metadataRecord };
|
|
918
|
+
delete strippedMetadata[PROMPTFOO_METADATA_KEY];
|
|
919
|
+
if (Object.keys(remainingPromptfooMetadata).length > 0) strippedMetadata[PROMPTFOO_METADATA_KEY] = remainingPromptfooMetadata;
|
|
920
|
+
return strippedMetadata;
|
|
921
|
+
}
|
|
922
|
+
function persistRepeatMetadata(metadata, repeatIndex, repeatGroupId) {
|
|
923
|
+
if (repeatIndex === void 0 || !repeatGroupId) return stripRepeatLinkageFromMetadata(metadata);
|
|
924
|
+
const metadataRecord = metadata ?? {};
|
|
925
|
+
const promptfooMetadata = asRecord(metadataRecord[PROMPTFOO_METADATA_KEY]);
|
|
926
|
+
if (metadataRecord["__promptfoo"] !== void 0 && promptfooMetadata === void 0) logger.warn(`[EvalResult] Overwriting non-object metadata.${PROMPTFOO_METADATA_KEY} with internal repeat linkage; the key is reserved for promptfoo internals.`);
|
|
927
|
+
if (promptfooMetadata && REPEAT_LINKAGE_KEY in promptfooMetadata) logger.warn(`[EvalResult] Overwriting metadata.${PROMPTFOO_METADATA_KEY}.${REPEAT_LINKAGE_KEY} with internal repeat linkage; the path is reserved for promptfoo internals.`);
|
|
928
|
+
return {
|
|
929
|
+
...metadataRecord,
|
|
930
|
+
[PROMPTFOO_METADATA_KEY]: {
|
|
931
|
+
...promptfooMetadata ?? {},
|
|
932
|
+
[REPEAT_LINKAGE_KEY]: {
|
|
933
|
+
repeatIndex,
|
|
934
|
+
repeatGroupId
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function stripRepeatLinkageFromMetadata(metadata) {
|
|
940
|
+
const metadataRecord = asRecord(metadata);
|
|
941
|
+
const promptfooMetadata = asRecord(metadataRecord?.[PROMPTFOO_METADATA_KEY]);
|
|
942
|
+
if (!metadataRecord || !promptfooMetadata || !(REPEAT_LINKAGE_KEY in promptfooMetadata)) return metadata;
|
|
943
|
+
const { [REPEAT_LINKAGE_KEY]: _repeatLinkage, ...remainingPromptfooMetadata } = promptfooMetadata;
|
|
944
|
+
const strippedMetadata = { ...metadataRecord };
|
|
945
|
+
delete strippedMetadata[PROMPTFOO_METADATA_KEY];
|
|
946
|
+
if (Object.keys(remainingPromptfooMetadata).length > 0) strippedMetadata[PROMPTFOO_METADATA_KEY] = remainingPromptfooMetadata;
|
|
947
|
+
return strippedMetadata;
|
|
948
|
+
}
|
|
949
|
+
function surfaceRepeatMetadata(metadata) {
|
|
950
|
+
const metadataRecord = metadata ?? {};
|
|
951
|
+
const promptfooMetadata = asRecord(metadataRecord[PROMPTFOO_METADATA_KEY]);
|
|
952
|
+
const repeatLinkage = asRecord(promptfooMetadata?.[REPEAT_LINKAGE_KEY]);
|
|
953
|
+
return {
|
|
954
|
+
repeatIndex: typeof repeatLinkage?.repeatIndex === "number" && Number.isInteger(repeatLinkage.repeatIndex) && repeatLinkage.repeatIndex >= 0 ? repeatLinkage.repeatIndex : void 0,
|
|
955
|
+
repeatGroupId: typeof repeatLinkage?.repeatGroupId === "string" && repeatLinkage.repeatGroupId.length > 0 ? repeatLinkage.repeatGroupId : void 0,
|
|
956
|
+
metadata: promptfooMetadata != null && REPEAT_LINKAGE_KEY in promptfooMetadata ? stripRepeatLinkageFromMetadata(metadataRecord) : metadataRecord
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function surfaceTraceMetadata(metadata) {
|
|
960
|
+
const metadataRecord = metadata ?? {};
|
|
961
|
+
const promptfooMetadata = asRecord(metadataRecord[PROMPTFOO_METADATA_KEY]);
|
|
962
|
+
const traceLinkage = asRecord(promptfooMetadata?.[TRACE_LINKAGE_KEY]);
|
|
963
|
+
const traceId = typeof traceLinkage?.traceId === "string" ? traceLinkage.traceId : void 0;
|
|
964
|
+
const evaluationId = typeof traceLinkage?.evaluationId === "string" ? traceLinkage.evaluationId : void 0;
|
|
965
|
+
if (!(promptfooMetadata != null && TRACE_LINKAGE_KEY in promptfooMetadata)) return {
|
|
966
|
+
traceId,
|
|
967
|
+
evaluationId,
|
|
968
|
+
metadata: metadataRecord
|
|
969
|
+
};
|
|
970
|
+
return {
|
|
971
|
+
traceId,
|
|
972
|
+
evaluationId,
|
|
973
|
+
metadata: stripTraceLinkageFromMetadata(metadataRecord)
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function redactSensitiveResultFieldsForDb(fields) {
|
|
977
|
+
return {
|
|
978
|
+
response: sanitizeResponseForDb(fields.response),
|
|
979
|
+
gradingResult: sanitizeGradingResultForDb(fields.gradingResult),
|
|
980
|
+
metadata: sanitizeMetadataForDb(fields.metadata, fields.response?.metadata)
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function getStripFlags(env) {
|
|
984
|
+
const getFlag = (key) => {
|
|
985
|
+
const value = env?.[key];
|
|
986
|
+
return value === void 0 ? getEnvBool(key, false) : parseEnvBool(String(value), false);
|
|
987
|
+
};
|
|
988
|
+
return {
|
|
989
|
+
shouldStripPromptText: getFlag("PROMPTFOO_STRIP_PROMPT_TEXT"),
|
|
990
|
+
shouldStripResponseOutput: getFlag("PROMPTFOO_STRIP_RESPONSE_OUTPUT"),
|
|
991
|
+
shouldStripTestVars: getFlag("PROMPTFOO_STRIP_TEST_VARS"),
|
|
992
|
+
shouldStripGradingResult: getFlag("PROMPTFOO_STRIP_GRADING_RESULT"),
|
|
993
|
+
shouldStripMetadata: getFlag("PROMPTFOO_STRIP_METADATA")
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Sanitize a result before it is serialized into a JSONL output artifact. This is the
|
|
998
|
+
* JSONL-boundary equivalent of the database-persistence sanitization and must stay in sync
|
|
999
|
+
* with it: it redacts credential-bearing HTTP headers from the response / grading / metadata
|
|
1000
|
+
* and applies the `PROMPTFOO_STRIP_*` projections (prompt text, response output, test vars,
|
|
1001
|
+
* grading result, metadata). In-memory rows keep their real values for hooks; only the
|
|
1002
|
+
* on-disk copy is sanitized.
|
|
1003
|
+
*/
|
|
1004
|
+
function sanitizeResultForJsonlArtifact(result, stripFlags = getStripFlags()) {
|
|
1005
|
+
const { shouldStripPromptText, shouldStripResponseOutput, shouldStripTestVars, shouldStripGradingResult, shouldStripMetadata } = stripFlags;
|
|
1006
|
+
const artifactResult = serializeResultProviderRefs(result);
|
|
1007
|
+
const redacted = redactSensitiveResultFieldsForDb({
|
|
1008
|
+
response: sanitizeForDb(artifactResult.response),
|
|
1009
|
+
gradingResult: sanitizeForDb(artifactResult.gradingResult),
|
|
1010
|
+
metadata: sanitizeForDb(artifactResult.metadata)
|
|
1011
|
+
});
|
|
1012
|
+
const response = projectProviderResponse(redacted.response ?? void 0, {
|
|
1013
|
+
stripMetadata: shouldStripMetadata,
|
|
1014
|
+
stripOutput: shouldStripResponseOutput
|
|
1015
|
+
});
|
|
1016
|
+
return {
|
|
1017
|
+
...result,
|
|
1018
|
+
...artifactResult.testCase ? { testCase: projectTestCase(sanitizeForDbWithSecrets(artifactResult.testCase), {
|
|
1019
|
+
stripMetadata: shouldStripMetadata,
|
|
1020
|
+
stripVars: shouldStripTestVars,
|
|
1021
|
+
stripOutput: shouldStripResponseOutput,
|
|
1022
|
+
stripPromptText: shouldStripPromptText
|
|
1023
|
+
}) } : {},
|
|
1024
|
+
...artifactResult.vars === void 0 ? {} : { vars: shouldStripTestVars ? {} : sanitizeForDbWithSecrets(artifactResult.vars) },
|
|
1025
|
+
...artifactResult.prompt ? { prompt: projectPrompt(sanitizeForDbWithSecrets(artifactResult.prompt), shouldStripPromptText) } : {},
|
|
1026
|
+
...artifactResult.provider ? { provider: sanitizeProvider(artifactResult.provider) } : {},
|
|
1027
|
+
response,
|
|
1028
|
+
gradingResult: shouldStripGradingResult ? null : projectGradingResult(redacted.gradingResult, shouldStripPromptText),
|
|
1029
|
+
namedScores: sanitizeForDb(artifactResult.namedScores),
|
|
1030
|
+
metadata: shouldStripMetadata ? {} : projectOutputMetadata(redacted.metadata, shouldStripResponseOutput, redacted.response?.metadata, artifactResult.testCase?.metadata)
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
var EvalResult = class EvalResult {
|
|
1034
|
+
static async createFromEvaluateResult(evalId, result, opts) {
|
|
1035
|
+
const persist = opts?.persist == null ? true : opts.persist;
|
|
1036
|
+
const { prompt, error, score, latencyMs, success, provider, gradingResult, namedScores, cost, metadata, failureReason, testCase, traceId, evaluationId, repeatIndex, repeatGroupId } = serializeResultProviderRefs(result);
|
|
1037
|
+
const persistedMetadata = persistTraceMetadata(persistRepeatMetadata(metadata, repeatIndex, repeatGroupId), traceId, evaluationId);
|
|
1038
|
+
const processedResponse = await extractAndStoreBinaryData(result.response, {
|
|
1039
|
+
evalId,
|
|
1040
|
+
testIdx: result.testIdx,
|
|
1041
|
+
promptIdx: result.promptIdx
|
|
1042
|
+
});
|
|
1043
|
+
const args = {
|
|
1044
|
+
id: crypto.randomUUID(),
|
|
1045
|
+
evalId,
|
|
1046
|
+
testCase: sanitizeForDbWithSecrets(testCase),
|
|
1047
|
+
promptIdx: result.promptIdx,
|
|
1048
|
+
testIdx: result.testIdx,
|
|
1049
|
+
prompt: sanitizeForDbWithSecrets(prompt),
|
|
1050
|
+
promptId: hashPrompt(prompt),
|
|
1051
|
+
error: error?.toString(),
|
|
1052
|
+
success,
|
|
1053
|
+
score: score == null ? 0 : score,
|
|
1054
|
+
response: sanitizeForDb(processedResponse || null),
|
|
1055
|
+
gradingResult: sanitizeForDb(gradingResult || null),
|
|
1056
|
+
namedScores: sanitizeForDb(namedScores),
|
|
1057
|
+
provider: sanitizeProvider(provider),
|
|
1058
|
+
latencyMs,
|
|
1059
|
+
cost,
|
|
1060
|
+
metadata: sanitizeForDb(persistedMetadata),
|
|
1061
|
+
failureReason
|
|
1062
|
+
};
|
|
1063
|
+
if (persist) {
|
|
1064
|
+
const db = await getDb();
|
|
1065
|
+
const redacted = redactSensitiveResultFieldsForDb({
|
|
1066
|
+
response: args.response,
|
|
1067
|
+
gradingResult: args.gradingResult,
|
|
1068
|
+
metadata: args.metadata
|
|
1069
|
+
});
|
|
1070
|
+
args.response = redacted.response;
|
|
1071
|
+
args.gradingResult = redacted.gradingResult;
|
|
1072
|
+
args.metadata = redacted.metadata;
|
|
1073
|
+
const dbResult = await db.insert(evalResultsTable).values(args).returning();
|
|
1074
|
+
clearCountCache(evalId);
|
|
1075
|
+
return new EvalResult({
|
|
1076
|
+
...dbResult[0],
|
|
1077
|
+
persisted: true
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
return new EvalResult(args);
|
|
1081
|
+
}
|
|
1082
|
+
static async createManyFromEvaluateResult(results, evalId) {
|
|
1083
|
+
const db = await getDb();
|
|
1084
|
+
const returnResults = [];
|
|
1085
|
+
const processedResults = [];
|
|
1086
|
+
for (const result of results) {
|
|
1087
|
+
const processedResponse = isBlobStorageEnabled() ? await extractAndStoreBinaryData(result.response, {
|
|
1088
|
+
evalId,
|
|
1089
|
+
testIdx: result.testIdx,
|
|
1090
|
+
promptIdx: result.promptIdx
|
|
1091
|
+
}) : result.response;
|
|
1092
|
+
processedResults.push({
|
|
1093
|
+
...serializeResultProviderRefs(result),
|
|
1094
|
+
response: processedResponse ?? void 0
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
await db.transaction(async (tx) => {
|
|
1098
|
+
for (const result of processedResults) {
|
|
1099
|
+
const { traceId: _traceId, evaluationId: _evaluationId, repeatIndex: _repeatIndex, repeatGroupId: _repeatGroupId, ...rest } = result;
|
|
1100
|
+
const sanitizedResult = {
|
|
1101
|
+
...rest,
|
|
1102
|
+
testCase: sanitizeForDbWithSecrets(result.testCase),
|
|
1103
|
+
prompt: sanitizeForDbWithSecrets(result.prompt),
|
|
1104
|
+
...redactSensitiveResultFieldsForDb({
|
|
1105
|
+
response: sanitizeForDb(result.response),
|
|
1106
|
+
gradingResult: sanitizeForDb(result.gradingResult),
|
|
1107
|
+
metadata: sanitizeForDb(persistTraceMetadata(persistRepeatMetadata(result.metadata, result.repeatIndex, result.repeatGroupId), result.traceId, result.evaluationId))
|
|
1108
|
+
}),
|
|
1109
|
+
namedScores: sanitizeForDb(result.namedScores),
|
|
1110
|
+
provider: result.provider ? sanitizeProvider(result.provider) : result.provider
|
|
1111
|
+
};
|
|
1112
|
+
const dbResult = await tx.insert(evalResultsTable).values({
|
|
1113
|
+
...sanitizedResult,
|
|
1114
|
+
evalId,
|
|
1115
|
+
id: crypto.randomUUID()
|
|
1116
|
+
}).returning().get();
|
|
1117
|
+
returnResults.push(new EvalResult({
|
|
1118
|
+
...dbResult,
|
|
1119
|
+
persisted: true
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
clearCountCache(evalId);
|
|
1124
|
+
return returnResults;
|
|
1125
|
+
}
|
|
1126
|
+
static async findById(id) {
|
|
1127
|
+
const result = await (await getDb()).select().from(evalResultsTable).where(eq(evalResultsTable.id, id));
|
|
1128
|
+
return result.length > 0 ? new EvalResult({
|
|
1129
|
+
...result[0],
|
|
1130
|
+
persisted: true
|
|
1131
|
+
}) : null;
|
|
1132
|
+
}
|
|
1133
|
+
static async findManyByEvalId(evalId, opts) {
|
|
1134
|
+
return (await (await getDb()).select().from(evalResultsTable).where(and(eq(evalResultsTable.evalId, evalId), opts?.testIdx == null ? void 0 : eq(evalResultsTable.testIdx, opts.testIdx)))).map((result) => new EvalResult({
|
|
1135
|
+
...result,
|
|
1136
|
+
persisted: true
|
|
1137
|
+
}));
|
|
1138
|
+
}
|
|
1139
|
+
static async findManyByEvalIdAndTestIndices(evalId, testIndices) {
|
|
1140
|
+
if (!testIndices.length) return [];
|
|
1141
|
+
return (await (await getDb()).select().from(evalResultsTable).where(and(eq(evalResultsTable.evalId, evalId), testIndices.length === 1 ? eq(evalResultsTable.testIdx, testIndices[0]) : inArray(evalResultsTable.testIdx, testIndices)))).map((result) => new EvalResult({
|
|
1142
|
+
...result,
|
|
1143
|
+
persisted: true
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Returns a set of completed (testIdx,promptIdx) pairs for a given eval.
|
|
1148
|
+
* Key format: `${testIdx}:${promptIdx}`
|
|
1149
|
+
*
|
|
1150
|
+
* @param evalId - The evaluation ID to query
|
|
1151
|
+
* @param opts.excludeErrors - If true, excludes results with ERROR failureReason (used in retry mode)
|
|
1152
|
+
*/
|
|
1153
|
+
static async getCompletedIndexPairs(evalId, opts) {
|
|
1154
|
+
const db = await getDb();
|
|
1155
|
+
const whereClause = opts?.excludeErrors ? and(eq(evalResultsTable.evalId, evalId), ne(evalResultsTable.failureReason, ResultFailureReason.ERROR)) : eq(evalResultsTable.evalId, evalId);
|
|
1156
|
+
const rows = await db.select({
|
|
1157
|
+
testIdx: evalResultsTable.testIdx,
|
|
1158
|
+
promptIdx: evalResultsTable.promptIdx
|
|
1159
|
+
}).from(evalResultsTable).where(whereClause);
|
|
1160
|
+
const ret = /* @__PURE__ */ new Set();
|
|
1161
|
+
for (const r of rows) ret.add(`${r.testIdx}:${r.promptIdx}`);
|
|
1162
|
+
return ret;
|
|
1163
|
+
}
|
|
1164
|
+
static async *findManyByEvalIdBatched(evalId, opts) {
|
|
1165
|
+
const db = await getDb();
|
|
1166
|
+
const batchSize = opts?.batchSize || 100;
|
|
1167
|
+
let offset = 0;
|
|
1168
|
+
while (true) {
|
|
1169
|
+
const nextResult = await db.select({ testIdx: evalResultsTable.testIdx }).from(evalResultsTable).where(and(eq(evalResultsTable.evalId, evalId), gte(evalResultsTable.testIdx, offset))).orderBy(evalResultsTable.testIdx).limit(1).get();
|
|
1170
|
+
if (!nextResult) break;
|
|
1171
|
+
offset = nextResult.testIdx;
|
|
1172
|
+
yield (await db.select().from(evalResultsTable).where(and(eq(evalResultsTable.evalId, evalId), gte(evalResultsTable.testIdx, offset), lt(evalResultsTable.testIdx, offset + batchSize))).all()).map((result) => new EvalResult({
|
|
1173
|
+
...result,
|
|
1174
|
+
persisted: true
|
|
1175
|
+
}));
|
|
1176
|
+
offset += batchSize;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
id;
|
|
1180
|
+
evalId;
|
|
1181
|
+
description;
|
|
1182
|
+
promptIdx;
|
|
1183
|
+
testIdx;
|
|
1184
|
+
testCase;
|
|
1185
|
+
prompt;
|
|
1186
|
+
promptId;
|
|
1187
|
+
error;
|
|
1188
|
+
success;
|
|
1189
|
+
score;
|
|
1190
|
+
response;
|
|
1191
|
+
gradingResult;
|
|
1192
|
+
namedScores;
|
|
1193
|
+
provider;
|
|
1194
|
+
latencyMs;
|
|
1195
|
+
cost;
|
|
1196
|
+
metadata;
|
|
1197
|
+
traceId;
|
|
1198
|
+
evaluationId;
|
|
1199
|
+
repeatIndex;
|
|
1200
|
+
repeatGroupId;
|
|
1201
|
+
failureReason;
|
|
1202
|
+
persisted;
|
|
1203
|
+
pluginId;
|
|
1204
|
+
constructor(opts) {
|
|
1205
|
+
this.id = opts.id;
|
|
1206
|
+
this.evalId = opts.evalId;
|
|
1207
|
+
this.promptIdx = opts.promptIdx;
|
|
1208
|
+
this.testIdx = opts.testIdx;
|
|
1209
|
+
this.testCase = opts.testCase;
|
|
1210
|
+
this.prompt = opts.prompt;
|
|
1211
|
+
this.promptId = opts.promptId || hashPrompt(opts.prompt);
|
|
1212
|
+
this.error = opts.error;
|
|
1213
|
+
this.score = opts.score;
|
|
1214
|
+
this.success = opts.success;
|
|
1215
|
+
this.response = opts.response || void 0;
|
|
1216
|
+
this.gradingResult = opts.gradingResult;
|
|
1217
|
+
this.namedScores = opts.namedScores || {};
|
|
1218
|
+
this.provider = opts.provider;
|
|
1219
|
+
this.latencyMs = opts.latencyMs || 0;
|
|
1220
|
+
this.cost = opts.cost || 0;
|
|
1221
|
+
const surfacedTrace = surfaceTraceMetadata(opts.metadata);
|
|
1222
|
+
const surfacedRepeat = surfaceRepeatMetadata(surfacedTrace.metadata);
|
|
1223
|
+
this.metadata = surfacedRepeat.metadata;
|
|
1224
|
+
this.traceId = surfacedTrace.traceId;
|
|
1225
|
+
this.evaluationId = surfacedTrace.evaluationId;
|
|
1226
|
+
this.repeatIndex = surfacedRepeat.repeatIndex;
|
|
1227
|
+
this.repeatGroupId = surfacedRepeat.repeatGroupId;
|
|
1228
|
+
this.failureReason = isResultFailureReason(opts.failureReason) ? opts.failureReason : ResultFailureReason.NONE;
|
|
1229
|
+
this.persisted = opts.persisted || false;
|
|
1230
|
+
this.pluginId = opts.testCase.metadata?.pluginId;
|
|
1231
|
+
}
|
|
1232
|
+
async save() {
|
|
1233
|
+
const db = await getDb();
|
|
1234
|
+
const { traceId: _traceId, evaluationId: _evaluationId, repeatIndex: _repeatIndex, repeatGroupId: _repeatGroupId, pluginId: _pluginId, ...rest } = serializeResultProviderRefs(this);
|
|
1235
|
+
const persistedValues = {
|
|
1236
|
+
...rest,
|
|
1237
|
+
error: this.error ?? null,
|
|
1238
|
+
gradingResult: sanitizeGradingResultForDb(rest.gradingResult),
|
|
1239
|
+
metadata: persistTraceMetadata(persistRepeatMetadata(this.metadata, this.repeatIndex, this.repeatGroupId), this.traceId, this.evaluationId)
|
|
1240
|
+
};
|
|
1241
|
+
if (this.persisted) await db.update(evalResultsTable).set({
|
|
1242
|
+
...persistedValues,
|
|
1243
|
+
updatedAt: getCurrentTimestamp()
|
|
1244
|
+
}).where(eq(evalResultsTable.id, this.id)).run();
|
|
1245
|
+
else {
|
|
1246
|
+
const result = await db.insert(evalResultsTable).values(persistedValues).returning();
|
|
1247
|
+
this.id = result[0].id;
|
|
1248
|
+
this.persisted = true;
|
|
1249
|
+
}
|
|
1250
|
+
invalidateEvaluationCache(this.evalId);
|
|
1251
|
+
}
|
|
1252
|
+
toEvaluateResult(stripFlags = getStripFlags()) {
|
|
1253
|
+
const { shouldStripPromptText, shouldStripResponseOutput, shouldStripTestVars, shouldStripGradingResult, shouldStripMetadata } = stripFlags;
|
|
1254
|
+
const response = projectProviderResponse(this.response, {
|
|
1255
|
+
stripMetadata: shouldStripMetadata,
|
|
1256
|
+
stripOutput: shouldStripResponseOutput
|
|
1257
|
+
});
|
|
1258
|
+
const prompt = projectPrompt(this.prompt, shouldStripPromptText);
|
|
1259
|
+
const testCase = projectTestCase(this.testCase, {
|
|
1260
|
+
stripMetadata: shouldStripMetadata,
|
|
1261
|
+
stripVars: shouldStripTestVars,
|
|
1262
|
+
stripOutput: shouldStripResponseOutput,
|
|
1263
|
+
stripPromptText: shouldStripPromptText
|
|
1264
|
+
});
|
|
1265
|
+
const tokenUsage = createEmptyTokenUsage();
|
|
1266
|
+
if (this.response) accumulateResponseTokenUsage(tokenUsage, this.response);
|
|
1267
|
+
if (this.gradingResult) accumulateGradingTokenUsage(tokenUsage, this.gradingResult.tokensUsed, { cached: this.gradingResult.metadata?.cachedResponse });
|
|
1268
|
+
return {
|
|
1269
|
+
cost: this.cost,
|
|
1270
|
+
...this.response?.incurredCost !== void 0 && { incurredCost: this.response.incurredCost },
|
|
1271
|
+
description: this.description || void 0,
|
|
1272
|
+
error: this.error || void 0,
|
|
1273
|
+
gradingResult: shouldStripGradingResult ? null : projectGradingResult(this.gradingResult, shouldStripPromptText),
|
|
1274
|
+
id: this.id,
|
|
1275
|
+
latencyMs: this.latencyMs,
|
|
1276
|
+
namedScores: this.namedScores,
|
|
1277
|
+
prompt,
|
|
1278
|
+
promptId: this.promptId,
|
|
1279
|
+
promptIdx: this.promptIdx,
|
|
1280
|
+
...this.traceId ? { traceId: this.traceId } : {},
|
|
1281
|
+
...this.evaluationId ? { evaluationId: this.evaluationId } : {},
|
|
1282
|
+
...this.repeatGroupId !== void 0 && {
|
|
1283
|
+
repeatGroupId: this.repeatGroupId,
|
|
1284
|
+
repeatIndex: this.repeatIndex
|
|
1285
|
+
},
|
|
1286
|
+
provider: {
|
|
1287
|
+
id: this.provider.id,
|
|
1288
|
+
label: this.provider.label
|
|
1289
|
+
},
|
|
1290
|
+
response,
|
|
1291
|
+
score: this.score,
|
|
1292
|
+
success: this.success,
|
|
1293
|
+
testCase,
|
|
1294
|
+
testIdx: this.testIdx,
|
|
1295
|
+
tokenUsage,
|
|
1296
|
+
vars: shouldStripTestVars ? {} : this.testCase.vars || {},
|
|
1297
|
+
metadata: shouldStripMetadata ? {} : projectOutputMetadata(this.metadata, shouldStripResponseOutput, this.response?.metadata, this.testCase.metadata),
|
|
1298
|
+
failureReason: this.failureReason
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
/** Normalize an `EvalResult` model instance or a plain `EvaluateResult` to `EvaluateResult`. */
|
|
1303
|
+
function asEvaluateResult(result, stripFlags = getStripFlags()) {
|
|
1304
|
+
return "toEvaluateResult" in result ? result.toEvaluateResult(stripFlags) : result;
|
|
1305
|
+
}
|
|
1306
|
+
/** Canonical `testIdx:promptIdx` key used to dedupe/look up a result across the streaming,
|
|
1307
|
+
* recovery, and comparison paths. */
|
|
1308
|
+
function getResultIndexKey(result) {
|
|
1309
|
+
return `${result.testIdx}:${result.promptIdx}`;
|
|
1310
|
+
}
|
|
1311
|
+
//#endregion
|
|
1312
|
+
//#region src/util/providerRef.ts
|
|
1313
|
+
const PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
1314
|
+
"id",
|
|
1315
|
+
"label",
|
|
1316
|
+
"config",
|
|
1317
|
+
"prompts",
|
|
1318
|
+
"transform",
|
|
1319
|
+
"delay",
|
|
1320
|
+
"env",
|
|
1321
|
+
"inputs"
|
|
1322
|
+
]);
|
|
1323
|
+
/** Returns true if the value is a non-empty string suitable as a provider identifier. */
|
|
1324
|
+
function isValidProviderId(id) {
|
|
1325
|
+
return typeof id === "string" && id !== "";
|
|
1326
|
+
}
|
|
1327
|
+
function getProviderLabel(provider) {
|
|
1328
|
+
if ((typeof provider === "object" || typeof provider === "function") && provider !== null && "label" in provider && typeof provider.label === "string") return provider.label;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Resolves relative file paths in provider IDs to absolute paths for consistent matching.
|
|
1332
|
+
* Handles file://, exec:, python:, golang: prefixes and bare .js/.ts/.mjs paths.
|
|
1333
|
+
*/
|
|
1334
|
+
function canonicalizeProviderId(id) {
|
|
1335
|
+
if (id.startsWith("file://")) {
|
|
1336
|
+
const filePath = id.slice(7);
|
|
1337
|
+
return path.isAbsolute(filePath) ? id : `file://${path.resolve(filePath)}`;
|
|
1338
|
+
}
|
|
1339
|
+
for (const prefix of [
|
|
1340
|
+
"exec:",
|
|
1341
|
+
"python:",
|
|
1342
|
+
"golang:"
|
|
1343
|
+
]) if (id.startsWith(prefix)) {
|
|
1344
|
+
const filePath = id.slice(prefix.length);
|
|
1345
|
+
if (filePath.includes("/") || filePath.includes("\\")) return `${prefix}${path.resolve(filePath)}`;
|
|
1346
|
+
return id;
|
|
1347
|
+
}
|
|
1348
|
+
if ((id.endsWith(".js") || id.endsWith(".ts") || id.endsWith(".mjs")) && (id.includes("/") || id.includes("\\"))) return `file://${path.resolve(id)}`;
|
|
1349
|
+
return id;
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Returns true for provider refs that should be expanded from YAML/JSON config files.
|
|
1353
|
+
*/
|
|
1354
|
+
function isProviderConfigFileReference(providerPath) {
|
|
1355
|
+
return providerPath.startsWith("file://") && (providerPath.endsWith(".yaml") || providerPath.endsWith(".yml") || providerPath.endsWith(".json"));
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Reads a provider config file and normalizes single-provider and multi-provider files.
|
|
1359
|
+
* An explicit base path also scopes nested configuration files; otherwise their
|
|
1360
|
+
* caller scope is retained.
|
|
1361
|
+
* Returns a `wasArray` flag so callers can detect multi-provider files that require
|
|
1362
|
+
* `loadApiProviders` instead of `loadApiProvider`.
|
|
1363
|
+
*/
|
|
1364
|
+
function readProviderConfigFile(providerPath, basePath) {
|
|
1365
|
+
const relativePath = providerPath.slice(7);
|
|
1366
|
+
const resolvedPath = path.isAbsolute(relativePath) ? relativePath : path.join(basePath || process.cwd(), relativePath);
|
|
1367
|
+
let rawContent;
|
|
1368
|
+
try {
|
|
1369
|
+
rawContent = loadYaml(fs.readFileSync(resolvedPath, "utf8"));
|
|
1370
|
+
} catch (err) {
|
|
1371
|
+
throw new Error(`Failed to load provider config ${relativePath}: ${err instanceof Error ? err.message : err}`);
|
|
1372
|
+
}
|
|
1373
|
+
const fileContent = maybeLoadConfigFromExternalFile(rawContent, void 0, basePath);
|
|
1374
|
+
invariant(fileContent, `Provider config ${relativePath} is undefined`);
|
|
1375
|
+
return {
|
|
1376
|
+
configs: [fileContent].flat(),
|
|
1377
|
+
relativePath,
|
|
1378
|
+
wasArray: Array.isArray(fileContent)
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Loads provider config objects from a file-backed provider reference.
|
|
1383
|
+
*/
|
|
1384
|
+
function loadProviderConfigsFromFile(providerPath, basePath) {
|
|
1385
|
+
return readProviderConfigFile(providerPath, basePath).configs;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Pure, synchronous classifier that converts every supported provider reference shape
|
|
1389
|
+
* into a discriminated descriptor. Does not read files or instantiate providers.
|
|
1390
|
+
*/
|
|
1391
|
+
function normalizeProviderRef(provider, options = {}) {
|
|
1392
|
+
const { index } = options;
|
|
1393
|
+
if (typeof provider === "string") {
|
|
1394
|
+
if (!isValidProviderId(provider)) return {
|
|
1395
|
+
kind: "unknown",
|
|
1396
|
+
id: index === void 0 ? "unknown" : `unknown-${index}`
|
|
1397
|
+
};
|
|
1398
|
+
if (isProviderConfigFileReference(provider)) return {
|
|
1399
|
+
kind: "file",
|
|
1400
|
+
id: provider,
|
|
1401
|
+
loadProviderPath: provider
|
|
1402
|
+
};
|
|
1403
|
+
return {
|
|
1404
|
+
kind: "named",
|
|
1405
|
+
id: provider,
|
|
1406
|
+
loadProviderPath: provider
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
if (typeof provider === "function") {
|
|
1410
|
+
const label = getProviderLabel(provider);
|
|
1411
|
+
return {
|
|
1412
|
+
kind: "function",
|
|
1413
|
+
id: label ?? (index === void 0 ? "custom-function" : `custom-function-${index}`),
|
|
1414
|
+
label
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
if (typeof provider === "object" && provider !== null && !Array.isArray(provider)) {
|
|
1418
|
+
const providerId = provider.id;
|
|
1419
|
+
const label = getProviderLabel(provider);
|
|
1420
|
+
if (isValidProviderId(providerId)) return {
|
|
1421
|
+
kind: "options",
|
|
1422
|
+
id: providerId,
|
|
1423
|
+
label,
|
|
1424
|
+
loadOptions: provider,
|
|
1425
|
+
loadProviderPath: providerId
|
|
1426
|
+
};
|
|
1427
|
+
const keys = Object.keys(provider);
|
|
1428
|
+
if (keys.length === 1 && !PROVIDER_OPTION_KEYS.has(keys[0])) {
|
|
1429
|
+
const originalId = keys[0];
|
|
1430
|
+
const providerObject = provider[originalId];
|
|
1431
|
+
if (typeof providerObject === "object" && providerObject !== null && !Array.isArray(providerObject) && isValidProviderId(originalId)) {
|
|
1432
|
+
const id = isValidProviderId(providerObject.id) ? providerObject.id : originalId;
|
|
1433
|
+
return {
|
|
1434
|
+
kind: "map",
|
|
1435
|
+
id,
|
|
1436
|
+
label: getProviderLabel(providerObject),
|
|
1437
|
+
loadOptions: {
|
|
1438
|
+
...providerObject,
|
|
1439
|
+
id
|
|
1440
|
+
},
|
|
1441
|
+
loadProviderPath: originalId
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (isValidProviderId(label)) return {
|
|
1446
|
+
kind: "unknown",
|
|
1447
|
+
id: label,
|
|
1448
|
+
label
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
kind: "unknown",
|
|
1453
|
+
id: index === void 0 ? "unknown" : `unknown-${index}`
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
//#endregion
|
|
1457
|
+
//#region src/util/provider.ts
|
|
1458
|
+
function providerToIdentifier(provider) {
|
|
1459
|
+
if (!provider) return;
|
|
1460
|
+
if (typeof provider === "string") return canonicalizeProviderId(provider);
|
|
1461
|
+
const { label } = normalizeProviderRef(provider);
|
|
1462
|
+
if (label) return label;
|
|
1463
|
+
if (isApiProvider(provider)) return canonicalizeProviderId(provider.id());
|
|
1464
|
+
if (isProviderOptions(provider)) {
|
|
1465
|
+
if (provider.id) return canonicalizeProviderId(provider.id);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
if (typeof provider === "object" && "id" in provider && typeof provider.id === "string") return canonicalizeProviderId(provider.id);
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Gets the identifier string for a provider (label or id).
|
|
1472
|
+
*/
|
|
1473
|
+
function getProviderIdentifier(provider) {
|
|
1474
|
+
return provider.label || provider.id();
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Gets a descriptive identifier string for a provider, showing both label and ID when both exist.
|
|
1478
|
+
* Useful for error messages to help users debug provider reference issues.
|
|
1479
|
+
*/
|
|
1480
|
+
function getProviderDescription(provider) {
|
|
1481
|
+
const label = provider.label;
|
|
1482
|
+
const id = provider.id();
|
|
1483
|
+
if (label && label !== id) return `${label} (${id})`;
|
|
1484
|
+
return id;
|
|
1485
|
+
}
|
|
1486
|
+
function sanitizeProviderIdForLog(providerId) {
|
|
1487
|
+
const descriptionMatch = providerId.match(/^(\S+)(\s+\(.+\))$/);
|
|
1488
|
+
if (descriptionMatch) {
|
|
1489
|
+
const [, id, description] = descriptionMatch;
|
|
1490
|
+
return `${sanitizeProviderIdForLog(id)}${description}`;
|
|
1491
|
+
}
|
|
1492
|
+
return providerId.includes("://") || providerId.startsWith("/") ? sanitizeUrl(providerId) : providerId;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Checks if a provider reference matches a given provider.
|
|
1496
|
+
* Supports exact matching and wildcard patterns.
|
|
1497
|
+
*/
|
|
1498
|
+
function doesProviderRefMatch(ref, provider) {
|
|
1499
|
+
const label = provider.label;
|
|
1500
|
+
const id = provider.id();
|
|
1501
|
+
const canonicalRef = canonicalizeProviderId(ref);
|
|
1502
|
+
const canonicalId = canonicalizeProviderId(id);
|
|
1503
|
+
if (label && label === ref) return true;
|
|
1504
|
+
if (id === ref || canonicalId === canonicalRef) return true;
|
|
1505
|
+
if (ref.endsWith("*")) {
|
|
1506
|
+
const prefix = ref.slice(0, -1);
|
|
1507
|
+
if (label?.startsWith(prefix) || id.startsWith(prefix) || canonicalId.startsWith(prefix)) return true;
|
|
1508
|
+
}
|
|
1509
|
+
if (label?.startsWith(`${ref}:`) || id.startsWith(`${ref}:`) || canonicalId.startsWith(`${ref}:`)) return true;
|
|
1510
|
+
return false;
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Checks if a provider is allowed based on a list of allowed references.
|
|
1514
|
+
*/
|
|
1515
|
+
function isProviderAllowed(provider, allowedProviders) {
|
|
1516
|
+
if (!Array.isArray(allowedProviders)) return true;
|
|
1517
|
+
if (allowedProviders.length === 0) return false;
|
|
1518
|
+
return allowedProviders.some((ref) => doesProviderRefMatch(ref, provider));
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Detects if a provider uses OpenAI models.
|
|
1522
|
+
* This includes direct OpenAI providers and Azure OpenAI.
|
|
1523
|
+
*/
|
|
1524
|
+
function isOpenAiProvider(providerId) {
|
|
1525
|
+
const lowerProviderId = providerId.toLowerCase();
|
|
1526
|
+
if (lowerProviderId.startsWith("openai:")) return true;
|
|
1527
|
+
if (lowerProviderId.startsWith("azureopenai:")) return true;
|
|
1528
|
+
if (lowerProviderId.startsWith("azure:")) {
|
|
1529
|
+
if ([
|
|
1530
|
+
"gpt",
|
|
1531
|
+
"openai",
|
|
1532
|
+
"davinci",
|
|
1533
|
+
"curie",
|
|
1534
|
+
"babbage",
|
|
1535
|
+
"ada",
|
|
1536
|
+
"text-embedding",
|
|
1537
|
+
"whisper",
|
|
1538
|
+
"dall-e",
|
|
1539
|
+
"tts"
|
|
1540
|
+
].some((indicator) => lowerProviderId.includes(indicator))) return true;
|
|
1541
|
+
}
|
|
1542
|
+
return false;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Detects if a provider uses Anthropic/Claude models.
|
|
1546
|
+
* This includes direct Anthropic providers, Bedrock with Claude, and Vertex with Claude.
|
|
1547
|
+
*/
|
|
1548
|
+
function isAnthropicProvider(providerId) {
|
|
1549
|
+
const lowerProviderId = providerId.toLowerCase();
|
|
1550
|
+
if (lowerProviderId.startsWith("anthropic:")) return true;
|
|
1551
|
+
if (lowerProviderId.startsWith("bedrock:")) {
|
|
1552
|
+
if (lowerProviderId.includes("claude") || lowerProviderId.includes("anthropic")) return true;
|
|
1553
|
+
}
|
|
1554
|
+
if (lowerProviderId.startsWith("vertex:")) {
|
|
1555
|
+
if (lowerProviderId.includes("claude")) return true;
|
|
1556
|
+
}
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
const KNOWN_ENV_VARS = {
|
|
1560
|
+
openai: "OPENAI_API_KEY",
|
|
1561
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
1562
|
+
google: "GOOGLE_API_KEY",
|
|
1563
|
+
mistral: "MISTRAL_API_KEY",
|
|
1564
|
+
cohere: "COHERE_API_KEY",
|
|
1565
|
+
replicate: "REPLICATE_API_TOKEN",
|
|
1566
|
+
voyage: "VOYAGE_API_KEY",
|
|
1567
|
+
ai21: "AI21_API_KEY",
|
|
1568
|
+
xai: "XAI_API_KEY",
|
|
1569
|
+
groq: "GROQ_API_KEY",
|
|
1570
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
1571
|
+
meta: "MODEL_API_KEY",
|
|
1572
|
+
moonshot: "MOONSHOT_API_KEY",
|
|
1573
|
+
perplexity: "PERPLEXITY_API_KEY",
|
|
1574
|
+
hyperbolic: "HYPERBOLIC_API_KEY",
|
|
1575
|
+
cerebras: "CEREBRAS_API_KEY",
|
|
1576
|
+
togetherai: "TOGETHER_API_KEY",
|
|
1577
|
+
fal: "FAL_KEY",
|
|
1578
|
+
huggingface: "HF_TOKEN",
|
|
1579
|
+
"cloudflare-ai": "CLOUDFLARE_API_KEY"
|
|
1580
|
+
};
|
|
1581
|
+
function getDefaultEnvVar(providerId) {
|
|
1582
|
+
const prefix = providerId.split(":")[0];
|
|
1583
|
+
return KNOWN_ENV_VARS[prefix] || `${prefix.toUpperCase()}_API_KEY`;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Pre-checks providers for missing API keys before evaluation starts.
|
|
1587
|
+
* Assumes getApiKey() is side-effect free (no network calls or token refresh).
|
|
1588
|
+
* Returns stable provider IDs by default; descriptions are for user-facing diagnostics only.
|
|
1589
|
+
*/
|
|
1590
|
+
function checkProviderApiKeys(providers, options = {}) {
|
|
1591
|
+
const missingApiKeys = /* @__PURE__ */ new Map();
|
|
1592
|
+
for (const provider of providers) {
|
|
1593
|
+
const p = provider;
|
|
1594
|
+
if (typeof p.getApiKey !== "function") continue;
|
|
1595
|
+
if (provider.id().startsWith("azure:")) continue;
|
|
1596
|
+
const requiresKey = typeof p.requiresApiKey === "function" ? p.requiresApiKey() : p.config?.apiKeyRequired !== false;
|
|
1597
|
+
let apiKey;
|
|
1598
|
+
try {
|
|
1599
|
+
apiKey = p.getApiKey();
|
|
1600
|
+
} catch {
|
|
1601
|
+
apiKey = void 0;
|
|
1602
|
+
}
|
|
1603
|
+
if (requiresKey && !apiKey) {
|
|
1604
|
+
const envVar = p.config?.apiKeyEnvar || getDefaultEnvVar(provider.id());
|
|
1605
|
+
const providerIdentifier = options.useDescriptions ? getProviderDescription(provider) : provider.id();
|
|
1606
|
+
if (!missingApiKeys.has(envVar)) missingApiKeys.set(envVar, []);
|
|
1607
|
+
missingApiKeys.get(envVar).push(providerIdentifier);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return missingApiKeys;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Detects if a provider uses Google models.
|
|
1614
|
+
* This includes direct Google/Vertex providers with Gemini and other Google models.
|
|
1615
|
+
* Note: Vertex with Claude models is NOT counted as Google (it's Anthropic).
|
|
1616
|
+
*/
|
|
1617
|
+
function isGoogleProvider(providerId) {
|
|
1618
|
+
const lowerProviderId = providerId.toLowerCase();
|
|
1619
|
+
if (lowerProviderId.startsWith("google:")) return true;
|
|
1620
|
+
if (lowerProviderId.startsWith("vertex:")) {
|
|
1621
|
+
if (!lowerProviderId.includes("claude")) return true;
|
|
1622
|
+
}
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
//#endregion
|
|
1626
|
+
//#region src/util/comparison.ts
|
|
1627
|
+
/**
|
|
1628
|
+
* Explicit runtime variable names that don't follow the underscore convention.
|
|
1629
|
+
* These are added during evaluation but aren't part of the original test definition.
|
|
1630
|
+
*
|
|
1631
|
+
* - sessionId: Added by multi-turn strategy providers (GOAT, Crescendo)
|
|
1632
|
+
*
|
|
1633
|
+
* Note: Variables starting with underscore (e.g., _conversation) are automatically
|
|
1634
|
+
* treated as runtime variables and filtered out.
|
|
1635
|
+
*/
|
|
1636
|
+
const EXPLICIT_RUNTIME_VAR_KEYS = ["sessionId"];
|
|
1637
|
+
/**
|
|
1638
|
+
* Checks if a variable key is a runtime-only variable that should be filtered
|
|
1639
|
+
* when comparing test cases.
|
|
1640
|
+
*
|
|
1641
|
+
* Runtime variables are identified by:
|
|
1642
|
+
* 1. Starting with underscore (_) - convention for internal/runtime vars
|
|
1643
|
+
* 2. Being in the explicit runtime var list (for legacy vars like sessionId)
|
|
1644
|
+
*/
|
|
1645
|
+
function isRuntimeVar(key) {
|
|
1646
|
+
return key.startsWith("_") || EXPLICIT_RUNTIME_VAR_KEYS.includes(key);
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Filters out runtime-only variables that are added during evaluation
|
|
1650
|
+
* but aren't part of the original test definition.
|
|
1651
|
+
*
|
|
1652
|
+
* This is used when comparing test cases to determine if a result
|
|
1653
|
+
* corresponds to a particular test, regardless of runtime state.
|
|
1654
|
+
*
|
|
1655
|
+
* Runtime variables are identified by:
|
|
1656
|
+
* - Starting with underscore (e.g., _conversation, _metadata)
|
|
1657
|
+
* - Being in the explicit list (e.g., sessionId for backward compatibility)
|
|
1658
|
+
*/
|
|
1659
|
+
function filterRuntimeVars(vars) {
|
|
1660
|
+
if (!vars || typeof vars !== "object" || Array.isArray(vars)) return vars;
|
|
1661
|
+
const filtered = {};
|
|
1662
|
+
for (const [key, value] of Object.entries(vars)) if (!isRuntimeVar(key)) filtered[key] = value;
|
|
1663
|
+
return filtered;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Extracts only runtime variables from a vars object.
|
|
1667
|
+
* This is the inverse of filterRuntimeVars.
|
|
1668
|
+
*
|
|
1669
|
+
* Used to restore runtime state when re-running filtered tests.
|
|
1670
|
+
*/
|
|
1671
|
+
function extractRuntimeVars(vars) {
|
|
1672
|
+
if (!vars || typeof vars !== "object" || Array.isArray(vars)) return;
|
|
1673
|
+
const extracted = {};
|
|
1674
|
+
for (const [key, value] of Object.entries(vars)) if (isRuntimeVar(key)) extracted[key] = value;
|
|
1675
|
+
return Object.keys(extracted).length > 0 ? extracted : void 0;
|
|
1676
|
+
}
|
|
1677
|
+
function varsMatch(vars1, vars2) {
|
|
1678
|
+
return deepEqual(vars1, vars2);
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Generate a unique key for a test case for deduplication purposes.
|
|
1682
|
+
* Excludes runtime variables and includes strategyId to distinguish tests
|
|
1683
|
+
* with the same prompt but different strategies.
|
|
1684
|
+
*
|
|
1685
|
+
* @param testCase - The test case to generate a key for
|
|
1686
|
+
* @returns A JSON string that uniquely identifies the test case
|
|
1687
|
+
*/
|
|
1688
|
+
function getTestCaseDeduplicationKey(testCase) {
|
|
1689
|
+
const filteredVars = filterRuntimeVars(testCase.vars);
|
|
1690
|
+
const strategyId = testCase.metadata?.strategyId || "none";
|
|
1691
|
+
return JSON.stringify({
|
|
1692
|
+
vars: filteredVars,
|
|
1693
|
+
strategyId
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Deduplicates an array of test cases based on their vars and strategyId.
|
|
1698
|
+
* Tests with the same vars but different strategies are considered different.
|
|
1699
|
+
* Runtime variables (like _conversation, sessionId) are filtered out before comparison.
|
|
1700
|
+
*
|
|
1701
|
+
* @param tests - Array of test cases to deduplicate
|
|
1702
|
+
* @returns Deduplicated array of test cases
|
|
1703
|
+
*/
|
|
1704
|
+
function deduplicateTestCases(tests) {
|
|
1705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1706
|
+
return tests.filter((test) => {
|
|
1707
|
+
const key = getTestCaseDeduplicationKey(test);
|
|
1708
|
+
if (seen.has(key)) return false;
|
|
1709
|
+
seen.add(key);
|
|
1710
|
+
return true;
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
function resultIsForTestCase(result, testCase) {
|
|
1714
|
+
const testProviderId = testCase.provider ? providerToIdentifier(testCase.provider) : void 0;
|
|
1715
|
+
const resultProviderId = providerToIdentifier(result.provider);
|
|
1716
|
+
const providersMatch = !testProviderId || !resultProviderId || testProviderId === resultProviderId;
|
|
1717
|
+
const resultVars = filterRuntimeVars(result.vars);
|
|
1718
|
+
const testVars = filterRuntimeVars(testCase.vars);
|
|
1719
|
+
const doVarsMatch = varsMatch(testVars, resultVars);
|
|
1720
|
+
const isMatch = doVarsMatch && providersMatch;
|
|
1721
|
+
if (!isMatch) {
|
|
1722
|
+
const varKeys = testVars ? Object.keys(testVars).join(", ") : "none";
|
|
1723
|
+
logger.debug(`[resultIsForTestCase] No match: vars=${doVarsMatch}, providers=${providersMatch}`, {
|
|
1724
|
+
testProvider: testProviderId || "none",
|
|
1725
|
+
resultProvider: resultProviderId || "none",
|
|
1726
|
+
testVarKeys: varKeys
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
return isMatch;
|
|
1730
|
+
}
|
|
1731
|
+
//#endregion
|
|
1732
|
+
//#region src/util/env.ts
|
|
1733
|
+
/**
|
|
1734
|
+
* Load environment variables from .env file(s).
|
|
1735
|
+
* @param envPath - Single path, array of paths, or undefined for default .env loading.
|
|
1736
|
+
* When paths are explicitly specified, all files must exist or an error is thrown.
|
|
1737
|
+
* When multiple files are provided, later files override values from earlier files.
|
|
1738
|
+
*/
|
|
1739
|
+
function setupEnv(envPath, options = {}) {
|
|
1740
|
+
const paths = (Array.isArray(envPath) ? envPath : [envPath ?? ""]).flatMap((p) => p.split(",").map((s) => s.trim())).filter(Boolean);
|
|
1741
|
+
if (paths.length > 0) {
|
|
1742
|
+
for (const p of paths) if (!fs$2.existsSync(p)) throw new Error(`Environment file not found: ${p}`);
|
|
1743
|
+
if (paths.length === 1) logger.info(`Loading environment variables from ${paths[0]}`);
|
|
1744
|
+
else logger.info(`Loading environment variables from: ${paths.join(", ")}`);
|
|
1745
|
+
}
|
|
1746
|
+
const previousEnv = { ...options.processEnv };
|
|
1747
|
+
loadEnvFiles(paths.length > 0 ? paths : void 0, {
|
|
1748
|
+
...paths.length > 0 && { override: true },
|
|
1749
|
+
...options.processEnv && { processEnv: options.processEnv }
|
|
1750
|
+
});
|
|
1751
|
+
if (options.processEnv && paths.length === 0) {
|
|
1752
|
+
for (const key of Object.keys(options.processEnv)) if (!Object.prototype.hasOwnProperty.call(previousEnv, key) && process.env[key] !== void 0) delete options.processEnv[key];
|
|
1753
|
+
}
|
|
1754
|
+
if (options.refreshConfigDirectory) refreshConfigDirectoryPathFromEnv();
|
|
1755
|
+
}
|
|
1756
|
+
//#endregion
|
|
1757
|
+
//#region src/googleSheets.ts
|
|
1758
|
+
var GoogleSheetAuthenticationRequiredError = class extends Error {
|
|
1759
|
+
constructor(message) {
|
|
1760
|
+
super(message);
|
|
1761
|
+
this.name = "GoogleSheetAuthenticationRequiredError";
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
async function loadGoogleSheetsApi() {
|
|
1765
|
+
try {
|
|
1766
|
+
return await import("@googleapis/sheets");
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
if (isMissingPackageImportError(error, "@googleapis/sheets")) throw new Error("The @googleapis/sheets package is required for authenticated Google Sheets access. Install it with: npm install @googleapis/sheets");
|
|
1769
|
+
throw error;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
async function checkGoogleSheetAccess(url) {
|
|
1773
|
+
try {
|
|
1774
|
+
const response = await fetchWithProxy(url);
|
|
1775
|
+
if (response.ok) return {
|
|
1776
|
+
public: true,
|
|
1777
|
+
status: response.status
|
|
1778
|
+
};
|
|
1779
|
+
else return {
|
|
1780
|
+
public: false,
|
|
1781
|
+
status: response.status
|
|
1782
|
+
};
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
logger.error(`Error checking sheet access: ${error}`);
|
|
1785
|
+
return { public: false };
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function isHtmlExportResponse(response, body) {
|
|
1789
|
+
const contentType = response.headers?.get("content-type")?.toLowerCase() ?? "";
|
|
1790
|
+
if (contentType.includes("text/html")) return true;
|
|
1791
|
+
if (contentType.includes("text/csv") || contentType.includes("application/csv")) return false;
|
|
1792
|
+
const prefix = body.trimStart().slice(0, 32).toLowerCase();
|
|
1793
|
+
return prefix.startsWith("<!doctype html") || prefix.startsWith("<html");
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Returns the sheet id (`gid`) a Google Sheets URL selects. The browser keeps it in the fragment
|
|
1797
|
+
* (`/edit#gid=123`); newer URLs also repeat it in the query (`/edit?gid=123#gid=123`). It stays a
|
|
1798
|
+
* string so that sheet id 0 is still truthy.
|
|
1799
|
+
*/
|
|
1800
|
+
function getSheetGid(url) {
|
|
1801
|
+
const { searchParams, hash } = new URL(url);
|
|
1802
|
+
return searchParams.get("gid") || new URLSearchParams(hash.slice(1)).get("gid") || void 0;
|
|
1803
|
+
}
|
|
1804
|
+
async function fetchCsvFromGoogleSheetUnauthenticated(url) {
|
|
1805
|
+
const { parse: parseCsv } = await import("csv-parse/sync");
|
|
1806
|
+
const gid = getSheetGid(url);
|
|
1807
|
+
const csvUrl = `${url.replace(/\/edit.*$/, "/export")}?format=csv${gid ? `&gid=${gid}` : ""}`;
|
|
1808
|
+
const response = await fetchWithProxy(csvUrl);
|
|
1809
|
+
if (response.status !== 200) throw new Error(`Failed to fetch CSV from Google Sheets URL: ${url}`);
|
|
1810
|
+
const csvData = await response.text();
|
|
1811
|
+
if (isHtmlExportResponse(response, csvData)) throw new GoogleSheetAuthenticationRequiredError(`Failed to fetch CSV from Google Sheets URL: ${url}`);
|
|
1812
|
+
return parseCsv(csvData, { columns: true });
|
|
1813
|
+
}
|
|
1814
|
+
async function fetchCsvFromGoogleSheetAuthenticated(url) {
|
|
1815
|
+
const { sheets: googleSheets, auth: googleAuth } = await loadGoogleSheetsApi();
|
|
1816
|
+
const auth = new googleAuth.GoogleAuth({ scopes: ["https://www.googleapis.com/auth/spreadsheets.readonly"] });
|
|
1817
|
+
const sheets = googleSheets("v4");
|
|
1818
|
+
const match = url.match(/\/d\/([^/]+)/);
|
|
1819
|
+
if (!match) throw new Error(`Invalid Google Sheets URL: ${url}`);
|
|
1820
|
+
const spreadsheetId = match[1];
|
|
1821
|
+
let range;
|
|
1822
|
+
const gid = getSheetGid(url);
|
|
1823
|
+
if (gid) {
|
|
1824
|
+
const sheet = (await sheets.spreadsheets.get({
|
|
1825
|
+
spreadsheetId,
|
|
1826
|
+
auth
|
|
1827
|
+
})).data.sheets?.find((sheet) => sheet.properties?.sheetId === Number(gid));
|
|
1828
|
+
if (!sheet || !sheet.properties?.title) throw new Error(`Sheet not found for gid: ${gid}`);
|
|
1829
|
+
range = sheet.properties.title;
|
|
1830
|
+
} else {
|
|
1831
|
+
const firstSheet = (await sheets.spreadsheets.get({
|
|
1832
|
+
spreadsheetId,
|
|
1833
|
+
auth
|
|
1834
|
+
})).data.sheets?.[0];
|
|
1835
|
+
if (!firstSheet || !firstSheet.properties?.title) throw new Error(`No sheets found in spreadsheet`);
|
|
1836
|
+
range = firstSheet.properties.title;
|
|
1837
|
+
}
|
|
1838
|
+
const rows = (await sheets.spreadsheets.values.get({
|
|
1839
|
+
spreadsheetId,
|
|
1840
|
+
range,
|
|
1841
|
+
auth
|
|
1842
|
+
})).data.values;
|
|
1843
|
+
if (!rows?.length) throw new Error(`No data found in Google Sheets URL: ${url}`);
|
|
1844
|
+
const headers = rows[0];
|
|
1845
|
+
return rows.slice(1).map((row) => {
|
|
1846
|
+
const csvRow = {};
|
|
1847
|
+
headers.forEach((header, index) => {
|
|
1848
|
+
csvRow[header] = row[index] ?? "";
|
|
1849
|
+
});
|
|
1850
|
+
return csvRow;
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
async function fetchCsvFromGoogleSheet(url) {
|
|
1854
|
+
const access = await checkGoogleSheetAccess(url);
|
|
1855
|
+
const { public: isPublic } = access;
|
|
1856
|
+
logger.debug(`Google Sheets URL: ${url}, isPublic: ${isPublic}`);
|
|
1857
|
+
if (isPublic) try {
|
|
1858
|
+
return await fetchCsvFromGoogleSheetUnauthenticated(url);
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
if (error instanceof GoogleSheetAuthenticationRequiredError) return fetchCsvFromGoogleSheetAuthenticated(url);
|
|
1861
|
+
throw error;
|
|
1862
|
+
}
|
|
1863
|
+
if (!("status" in access)) try {
|
|
1864
|
+
return await fetchCsvFromGoogleSheetUnauthenticated(url);
|
|
1865
|
+
} catch {
|
|
1866
|
+
return fetchCsvFromGoogleSheetAuthenticated(url);
|
|
1867
|
+
}
|
|
1868
|
+
return fetchCsvFromGoogleSheetAuthenticated(url);
|
|
1869
|
+
}
|
|
1870
|
+
async function writeCsvToGoogleSheet(rows, url) {
|
|
1871
|
+
const { sheets: googleSheets, auth: googleAuth } = await loadGoogleSheetsApi();
|
|
1872
|
+
const auth = new googleAuth.GoogleAuth({ scopes: ["https://www.googleapis.com/auth/spreadsheets"] });
|
|
1873
|
+
const sheets = googleSheets("v4");
|
|
1874
|
+
const match = url.match(/\/d\/([^/]+)/);
|
|
1875
|
+
if (!match) throw new Error(`Invalid Google Sheets URL: ${url}`);
|
|
1876
|
+
const spreadsheetId = match[1];
|
|
1877
|
+
const headers = Object.keys(rows[0]);
|
|
1878
|
+
const values = [headers, ...rows.map((row) => headers.map((header) => row[header]))];
|
|
1879
|
+
const getColumnLetter = (col) => {
|
|
1880
|
+
let letter = "";
|
|
1881
|
+
while (col > 0) {
|
|
1882
|
+
col--;
|
|
1883
|
+
letter = String.fromCharCode(65 + col % 26) + letter;
|
|
1884
|
+
col = Math.floor(col / 26);
|
|
1885
|
+
}
|
|
1886
|
+
return letter;
|
|
1887
|
+
};
|
|
1888
|
+
const numRows = values.length;
|
|
1889
|
+
const numCols = headers.length;
|
|
1890
|
+
const endColumn = getColumnLetter(numCols);
|
|
1891
|
+
let range;
|
|
1892
|
+
const gid = getSheetGid(url);
|
|
1893
|
+
if (gid) {
|
|
1894
|
+
const sheet = (await sheets.spreadsheets.get({
|
|
1895
|
+
spreadsheetId,
|
|
1896
|
+
auth
|
|
1897
|
+
})).data.sheets?.find((sheet) => sheet.properties?.sheetId === Number(gid));
|
|
1898
|
+
if (!sheet || !sheet.properties?.title) throw new Error(`Sheet not found for gid: ${gid}`);
|
|
1899
|
+
range = `${sheet.properties.title}!A1:${endColumn}${numRows}`;
|
|
1900
|
+
} else {
|
|
1901
|
+
const newSheetTitle = `Sheet${Date.now()}`;
|
|
1902
|
+
await sheets.spreadsheets.batchUpdate({
|
|
1903
|
+
spreadsheetId,
|
|
1904
|
+
auth,
|
|
1905
|
+
requestBody: { requests: [{ addSheet: { properties: { title: newSheetTitle } } }] }
|
|
1906
|
+
});
|
|
1907
|
+
range = `${newSheetTitle}!A1:${endColumn}${numRows}`;
|
|
1908
|
+
}
|
|
1909
|
+
logger.debug(`Writing CSV to Google Sheets URL: ${url} with ${values.length} rows`);
|
|
1910
|
+
await sheets.spreadsheets.values.update({
|
|
1911
|
+
spreadsheetId,
|
|
1912
|
+
range,
|
|
1913
|
+
valueInputOption: "USER_ENTERED",
|
|
1914
|
+
auth,
|
|
1915
|
+
requestBody: { values }
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
//#endregion
|
|
1919
|
+
//#region src/util/eval/evalTableUtils.ts
|
|
1920
|
+
/**
|
|
1921
|
+
* Error thrown when a comparison eval is not found.
|
|
1922
|
+
*/
|
|
1923
|
+
var ComparisonEvalNotFoundError = class extends Error {
|
|
1924
|
+
constructor(evalId) {
|
|
1925
|
+
super(`Comparison eval not found: ${evalId}`);
|
|
1926
|
+
this.name = "ComparisonEvalNotFoundError";
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
/**
|
|
1930
|
+
* Keep this in its current order, as it is used to map the columns in the CSV, so it needs to be static.
|
|
1931
|
+
*
|
|
1932
|
+
* The keys are the names of the columns in the metadata object, and the values are the names of the columns in the CSV.
|
|
1933
|
+
*
|
|
1934
|
+
* This is imported by enterprise so it doesn't need to be copied.
|
|
1935
|
+
*/
|
|
1936
|
+
const REDTEAM_METADATA_KEYS_TO_CSV_COLUMN_NAMES = {
|
|
1937
|
+
messages: "Messages",
|
|
1938
|
+
redteamHistory: "RedteamHistory",
|
|
1939
|
+
redteamTreeHistory: "RedteamTreeHistory",
|
|
1940
|
+
pluginId: "pluginId",
|
|
1941
|
+
strategyId: "strategyId",
|
|
1942
|
+
sessionId: "sessionId",
|
|
1943
|
+
sessionIds: "sessionIds"
|
|
1944
|
+
};
|
|
1945
|
+
const REDTEAM_METADATA_COLUMNS = Object.values(REDTEAM_METADATA_KEYS_TO_CSV_COLUMN_NAMES);
|
|
1946
|
+
/**
|
|
1947
|
+
* Get the status string for an output
|
|
1948
|
+
*/
|
|
1949
|
+
function getOutputStatus(output) {
|
|
1950
|
+
if (output.pass) return "PASS";
|
|
1951
|
+
return output.failureReason === ResultFailureReason.ASSERT ? "FAIL" : "ERROR";
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Format named scores for CSV output.
|
|
1955
|
+
* Returns empty string if no named scores, otherwise JSON string.
|
|
1956
|
+
*/
|
|
1957
|
+
function formatNamedScores(namedScores) {
|
|
1958
|
+
if (!namedScores || Object.keys(namedScores).length === 0) return "";
|
|
1959
|
+
const rounded = {};
|
|
1960
|
+
for (const [key, value] of Object.entries(namedScores)) if (typeof value === "number" && !Number.isNaN(value)) rounded[key] = Number(value.toFixed(2));
|
|
1961
|
+
if (Object.keys(rounded).length === 0) return "";
|
|
1962
|
+
return JSON.stringify(rounded);
|
|
1963
|
+
}
|
|
1964
|
+
function formatNamedScoreValue(value) {
|
|
1965
|
+
return typeof value === "number" && !Number.isNaN(value) ? value.toFixed(2) : "";
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Build a per-prompt list of `Metric: <name>` column names, deduplicated and
|
|
1969
|
+
* sorted with `localeCompare`. Shared shell for both the row-scanning helper
|
|
1970
|
+
* (WebUI path) and the aggregate-reading helper (streaming CLI path); see the
|
|
1971
|
+
* two call sites below.
|
|
1972
|
+
*/
|
|
1973
|
+
function collectMetricNamesByPrompt(items, namesFor) {
|
|
1974
|
+
return items.map((item, index) => [...new Set(namesFor(item, index))].sort((a, b) => a.localeCompare(b)));
|
|
1975
|
+
}
|
|
1976
|
+
/**
|
|
1977
|
+
* Collect per-row named metric names by scanning row outputs. Used by the
|
|
1978
|
+
* non-streaming WebUI export, which has the full table in memory.
|
|
1979
|
+
*
|
|
1980
|
+
* Returns one sorted name list per prompt, aligned with `table.head.prompts`.
|
|
1981
|
+
* Names whose only values are non-numeric or NaN are skipped so we don't emit
|
|
1982
|
+
* blank columns for invalid data.
|
|
1983
|
+
*/
|
|
1984
|
+
function collectNamedScoreNamesByPrompt(table) {
|
|
1985
|
+
return collectMetricNamesByPrompt(table.head.prompts, (_, promptIndex) => {
|
|
1986
|
+
const names = [];
|
|
1987
|
+
for (const row of table.body) {
|
|
1988
|
+
const namedScores = row.outputs[promptIndex]?.namedScores;
|
|
1989
|
+
if (!namedScores) continue;
|
|
1990
|
+
for (const [name, value] of Object.entries(namedScores)) if (typeof value === "number" && !Number.isNaN(value)) names.push(name);
|
|
1991
|
+
}
|
|
1992
|
+
return names;
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Collect per-row named metric names from prompt-level aggregate metrics, for
|
|
1997
|
+
* use by the streaming CSV path which cannot scan rows up front.
|
|
1998
|
+
*
|
|
1999
|
+
* Uses `namedScoresCount` because it is incremented only when a per-row
|
|
2000
|
+
* result contributes a metric (`src/util/namedMetrics.ts`). Derived metrics
|
|
2001
|
+
* write to `namedScores` but not `namedScoresCount` (`src/evaluator.ts`
|
|
2002
|
+
* `updateDerivedMetrics`), so this keeps the streaming column set aligned
|
|
2003
|
+
* with what `collectNamedScoreNamesByPrompt` would produce from row outputs.
|
|
2004
|
+
*
|
|
2005
|
+
* Returns an empty list both for prompts with no per-row contributions
|
|
2006
|
+
* (`namedScoresCount: {}` — modern eval with no named metrics configured)
|
|
2007
|
+
* and for prompts whose count map is missing entirely (legacy/imported
|
|
2008
|
+
* evals). Use `promptNeedsDiscoveryFallback` to distinguish the two: only
|
|
2009
|
+
* the latter requires a row-scan pre-pass.
|
|
2010
|
+
*/
|
|
2011
|
+
function collectNamedScoreNamesByPromptFromAggregate(prompts) {
|
|
2012
|
+
return collectMetricNamesByPrompt(prompts, (prompt) => {
|
|
2013
|
+
const counts = prompt.metrics?.namedScoresCount;
|
|
2014
|
+
if (!counts) return [];
|
|
2015
|
+
return Object.keys(counts).filter((name) => (counts[name] ?? 0) > 0);
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* True when a prompt's aggregate `metrics.namedScoresCount` is missing/absent,
|
|
2020
|
+
* not merely empty. Legacy evals persisted before count tracking shipped, and
|
|
2021
|
+
* external imports via `POST /api/eval` that don't backfill the count, both
|
|
2022
|
+
* land here. Modern evals always have an object (possibly empty) and skip the
|
|
2023
|
+
* discovery pass.
|
|
2024
|
+
*/
|
|
2025
|
+
function promptNeedsDiscoveryFallback(prompt) {
|
|
2026
|
+
const metrics = prompt.metrics;
|
|
2027
|
+
return !metrics || !metrics.namedScoresCount;
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Scan one batch of `EvalResult` rows for the metric names that will need
|
|
2031
|
+
* dedicated `Metric: <name>` columns. Only inspects the prompts in
|
|
2032
|
+
* `fallbackPromptIndices` (those whose aggregate `namedScoresCount` is
|
|
2033
|
+
* missing/empty), and only touches `namedScores` + `testCase.description` —
|
|
2034
|
+
* not the full row shape — so the discovery pass stays allocation-light even
|
|
2035
|
+
* for very large legacy evals.
|
|
2036
|
+
*
|
|
2037
|
+
* Mirrors the row-output filter in `collectNamedScoreNamesByPrompt` so the
|
|
2038
|
+
* streaming CSV column set matches the WebUI CSV column set.
|
|
2039
|
+
*/
|
|
2040
|
+
function scanBatchForDiscovery(batchResults, acc, fallbackPromptIndices) {
|
|
2041
|
+
for (const result of batchResults) {
|
|
2042
|
+
if (!acc.hasDescriptions && result.testCase?.description) acc.hasDescriptions = true;
|
|
2043
|
+
if (!fallbackPromptIndices.has(result.promptIdx)) continue;
|
|
2044
|
+
const namedScores = result.namedScores;
|
|
2045
|
+
if (!namedScores) continue;
|
|
2046
|
+
let names = acc.metricNamesByPrompt.get(result.promptIdx);
|
|
2047
|
+
if (!names) {
|
|
2048
|
+
names = /* @__PURE__ */ new Set();
|
|
2049
|
+
acc.metricNamesByPrompt.set(result.promptIdx, names);
|
|
2050
|
+
}
|
|
2051
|
+
for (const [name, value] of Object.entries(namedScores)) if (typeof value === "number" && !Number.isNaN(value)) names.add(name);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Convert a batch of `EvalResult` rows (grouped by testIdx) into the table-row
|
|
2056
|
+
* shape that `tableRowToCsvValues` accepts. Used by `streamEvalCsv` to keep its
|
|
2057
|
+
* per-batch processing tight; the outputs array is pre-sized so columns line up
|
|
2058
|
+
* with prompts even when results arrive out of promptIdx order.
|
|
2059
|
+
*/
|
|
2060
|
+
function batchToStreamRows(batchResults, varNames, numPrompts) {
|
|
2061
|
+
const rowsByTestIdx = /* @__PURE__ */ new Map();
|
|
2062
|
+
for (const result of batchResults) {
|
|
2063
|
+
let row = rowsByTestIdx.get(result.testIdx);
|
|
2064
|
+
if (!row) {
|
|
2065
|
+
row = {
|
|
2066
|
+
vars: varNames.map((varName) => {
|
|
2067
|
+
const value = result.testCase?.vars?.[varName];
|
|
2068
|
+
return value === void 0 ? "" : String(value);
|
|
2069
|
+
}),
|
|
2070
|
+
outputs: new Array(numPrompts).fill(null),
|
|
2071
|
+
test: { description: result.testCase?.description }
|
|
2072
|
+
};
|
|
2073
|
+
rowsByTestIdx.set(result.testIdx, row);
|
|
2074
|
+
}
|
|
2075
|
+
row.outputs[result.promptIdx] = {
|
|
2076
|
+
text: result.response?.output ?? "",
|
|
2077
|
+
pass: result.success,
|
|
2078
|
+
score: result.score,
|
|
2079
|
+
namedScores: result.namedScores,
|
|
2080
|
+
failureReason: result.failureReason,
|
|
2081
|
+
gradingResult: result.gradingResult,
|
|
2082
|
+
metadata: result.metadata
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
return Array.from(rowsByTestIdx.values());
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Build CSV headers for an evaluation table.
|
|
2089
|
+
*
|
|
2090
|
+
* @param vars - Variable names from the table head
|
|
2091
|
+
* @param prompts - Prompt definitions from the table head
|
|
2092
|
+
* @param options - Export options
|
|
2093
|
+
* @param options.namedScoreNamesByPrompt - Named metric names that should get
|
|
2094
|
+
* dedicated `Metric: <name>` columns, one sorted list per prompt (aligned
|
|
2095
|
+
* with `prompts`). Must match the value passed to `tableRowToCsvValues` so
|
|
2096
|
+
* header and row column counts stay in sync.
|
|
2097
|
+
* @returns Array of header strings
|
|
2098
|
+
*/
|
|
2099
|
+
function buildCsvHeaders(vars, prompts, options = {}) {
|
|
2100
|
+
const headers = [
|
|
2101
|
+
...options.hasDescriptions ? ["Description"] : [],
|
|
2102
|
+
...vars,
|
|
2103
|
+
...prompts.flatMap((prompt, promptIndex) => {
|
|
2104
|
+
const provider = prompt.provider || "";
|
|
2105
|
+
return [
|
|
2106
|
+
provider ? `[${provider}] ${prompt.label}` : prompt.label,
|
|
2107
|
+
"Status",
|
|
2108
|
+
"Score",
|
|
2109
|
+
"Named Scores",
|
|
2110
|
+
...(options.namedScoreNamesByPrompt?.[promptIndex] || []).map((name) => `Metric: ${name}`),
|
|
2111
|
+
"Grader Reason",
|
|
2112
|
+
"Comment"
|
|
2113
|
+
];
|
|
2114
|
+
})
|
|
2115
|
+
];
|
|
2116
|
+
if (options.isRedteam) headers.push(...REDTEAM_METADATA_COLUMNS);
|
|
2117
|
+
return headers;
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Convert a single table row to CSV row values.
|
|
2121
|
+
*
|
|
2122
|
+
* @param row - The table row to convert
|
|
2123
|
+
* @param options - Export options (`namedScoreNamesByPrompt` must match the
|
|
2124
|
+
* array passed to `buildCsvHeaders`)
|
|
2125
|
+
* @returns Array of values for the CSV row
|
|
2126
|
+
*/
|
|
2127
|
+
function tableRowToCsvValues(row, options = {}) {
|
|
2128
|
+
const rowValues = [
|
|
2129
|
+
...options.hasDescriptions ? [row.test.description || ""] : [],
|
|
2130
|
+
...row.vars,
|
|
2131
|
+
...row.outputs.flatMap((output, outputIndex) => {
|
|
2132
|
+
const namedScoreNames = options.namedScoreNamesByPrompt?.[outputIndex] || [];
|
|
2133
|
+
if (!output) return [
|
|
2134
|
+
"",
|
|
2135
|
+
"",
|
|
2136
|
+
"",
|
|
2137
|
+
"",
|
|
2138
|
+
...namedScoreNames.map(() => ""),
|
|
2139
|
+
"",
|
|
2140
|
+
""
|
|
2141
|
+
];
|
|
2142
|
+
const status = getOutputStatus(output);
|
|
2143
|
+
const score = output.score?.toFixed(2) ?? "";
|
|
2144
|
+
const namedScores = formatNamedScores(output.namedScores);
|
|
2145
|
+
const namedScoreValues = namedScoreNames.map((name) => formatNamedScoreValue(output.namedScores?.[name]));
|
|
2146
|
+
return [
|
|
2147
|
+
output.text || "",
|
|
2148
|
+
status,
|
|
2149
|
+
score,
|
|
2150
|
+
namedScores,
|
|
2151
|
+
...namedScoreValues,
|
|
2152
|
+
output.gradingResult?.reason || "",
|
|
2153
|
+
output.gradingResult?.comment || ""
|
|
2154
|
+
];
|
|
2155
|
+
})
|
|
2156
|
+
];
|
|
2157
|
+
if (options.isRedteam) {
|
|
2158
|
+
const redteamKeys = Object.keys(REDTEAM_METADATA_KEYS_TO_CSV_COLUMN_NAMES);
|
|
2159
|
+
const firstOutputMetadata = row.outputs[0]?.metadata;
|
|
2160
|
+
for (const key of redteamKeys) {
|
|
2161
|
+
let value = firstOutputMetadata?.[key];
|
|
2162
|
+
if (key === "strategyId" && (value === null || value === void 0)) value = "basic";
|
|
2163
|
+
if (value === null || value === void 0) rowValues.push("");
|
|
2164
|
+
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") rowValues.push(value.toString());
|
|
2165
|
+
else rowValues.push(JSON.stringify(value));
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
return rowValues;
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Escape every cell of a CSV row matrix (headers + body) against spreadsheet
|
|
2172
|
+
* formula injection (CWE-1236), unless disabled via
|
|
2173
|
+
* `PROMPTFOO_DISABLE_CSV_FORMULA_ESCAPING`. Numbers and booleans pass through
|
|
2174
|
+
* untouched; only string cells are inspected. Applied at the `csvStringify`
|
|
2175
|
+
* boundary so `buildCsvHeaders`/`tableRowToCsvValues` stay pure and re-importable
|
|
2176
|
+
* CSVs are never produced from un-escaped data.
|
|
2177
|
+
*/
|
|
2178
|
+
function escapeCsvRowsForFormulaInjection(rows, env) {
|
|
2179
|
+
const configuredValue = env?.PROMPTFOO_DISABLE_CSV_FORMULA_ESCAPING ?? process.env.PROMPTFOO_DISABLE_CSV_FORMULA_ESCAPING;
|
|
2180
|
+
if ([
|
|
2181
|
+
"1",
|
|
2182
|
+
"true",
|
|
2183
|
+
"yes",
|
|
2184
|
+
"yup",
|
|
2185
|
+
"yeppers"
|
|
2186
|
+
].includes(configuredValue?.toLowerCase() ?? "")) return rows;
|
|
2187
|
+
return rows.map((row) => row.map((cell) => typeof cell === "string" ? escapeCsvFormula(cell) : cell));
|
|
2188
|
+
}
|
|
2189
|
+
function isNumericCell(value) {
|
|
2190
|
+
return value.trim() !== "" && Number.isFinite(Number(value));
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Neutralize spreadsheet formula (CSV) injection at eval export time (CWE-1236).
|
|
2194
|
+
*
|
|
2195
|
+
* Spreadsheet applications execute cells beginning with `=`, `+`, `-`, or `@`
|
|
2196
|
+
* as formulas. Exported eval data can contain attacker-controlled model output,
|
|
2197
|
+
* variables, and grader comments, and CSV quoting alone does not neutralize
|
|
2198
|
+
* those formulas. Prefix dangerous values with the spreadsheet-native text
|
|
2199
|
+
* marker while preserving finite signed numbers.
|
|
2200
|
+
*
|
|
2201
|
+
* Leading whitespace and zero-width characters are ignored when identifying a
|
|
2202
|
+
* trigger because spreadsheet applications can strip them before evaluation.
|
|
2203
|
+
*/
|
|
2204
|
+
function escapeCsvFormula(value) {
|
|
2205
|
+
if (value.length === 0) return value;
|
|
2206
|
+
const unpadded = value.replace(/^[\s\u200B\u200C\u200D]+/, "");
|
|
2207
|
+
const firstChar = unpadded[0];
|
|
2208
|
+
if (firstChar === void 0) return value;
|
|
2209
|
+
return firstChar === "=" || firstChar === "@" || (firstChar === "-" || firstChar === "+") && !isNumericCell(unpadded) ? `'${value}` : value;
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Generates CSV data from evaluation table data.
|
|
2213
|
+
*
|
|
2214
|
+
* Column structure per prompt:
|
|
2215
|
+
* - Output: Pure LLM output text (no pass/fail prefix)
|
|
2216
|
+
* - Status: PASS | FAIL | ERROR
|
|
2217
|
+
* - Score: Numeric score (e.g., "1.00")
|
|
2218
|
+
* - Named Scores: JSON object with per-assertion scores (e.g., {"clarity": 0.90, "accuracy": 0.85})
|
|
2219
|
+
* - Metric: <name>: One dedicated column per distinct per-row named score,
|
|
2220
|
+
* sorted alphabetically by name. Empty when a row has no value for that
|
|
2221
|
+
* metric. Derived metrics are intentionally omitted (they live on the
|
|
2222
|
+
* aggregate prompt metrics, not on individual rows).
|
|
2223
|
+
* - Grader Reason: Explanation from the grader
|
|
2224
|
+
* - Comment: Additional grader comment
|
|
2225
|
+
*
|
|
2226
|
+
* This function is the single source of truth for CSV generation,
|
|
2227
|
+
* used by both the WebUI export and CLI export.
|
|
2228
|
+
*
|
|
2229
|
+
* @param table - The evaluation table data
|
|
2230
|
+
* @param options - Export options
|
|
2231
|
+
* @returns CSV formatted string
|
|
2232
|
+
*/
|
|
2233
|
+
function evalTableToCsv(table, options = {}) {
|
|
2234
|
+
const { env, isRedteam = false } = options;
|
|
2235
|
+
const hasDescriptions = table.body.some((row) => row.test.description);
|
|
2236
|
+
const namedScoreNamesByPrompt = collectNamedScoreNamesByPrompt(table);
|
|
2237
|
+
const csvRows = [buildCsvHeaders(table.head.vars, table.head.prompts, {
|
|
2238
|
+
hasDescriptions,
|
|
2239
|
+
isRedteam,
|
|
2240
|
+
namedScoreNamesByPrompt
|
|
2241
|
+
}), ...table.body.map((row) => tableRowToCsvValues(row, {
|
|
2242
|
+
hasDescriptions,
|
|
2243
|
+
isRedteam,
|
|
2244
|
+
namedScoreNamesByPrompt
|
|
2245
|
+
}))];
|
|
2246
|
+
return stringify(escapeCsvRowsForFormulaInjection(csvRows, env));
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Generate JSON data from evaluation table
|
|
2250
|
+
* @param table Evaluation table data
|
|
2251
|
+
* @returns JSON object
|
|
2252
|
+
*/
|
|
2253
|
+
function evalTableToJson(table) {
|
|
2254
|
+
return table;
|
|
2255
|
+
}
|
|
2256
|
+
function getEvalTableOutputPromptLocationsBySize(payload) {
|
|
2257
|
+
return payload.table.body.flatMap((row, rowIndex) => row.outputs.flatMap((output, outputIndex) => output?.prompt ? [{
|
|
2258
|
+
rowIndex,
|
|
2259
|
+
outputIndex,
|
|
2260
|
+
length: output.prompt.length
|
|
2261
|
+
}] : [])).sort((a, b) => b.length - a.length);
|
|
2262
|
+
}
|
|
2263
|
+
function stripEvalTableOutputPrompts(payload, locationsToStrip) {
|
|
2264
|
+
return {
|
|
2265
|
+
...payload,
|
|
2266
|
+
table: {
|
|
2267
|
+
...payload.table,
|
|
2268
|
+
body: payload.table.body.map((row, rowIndex) => ({
|
|
2269
|
+
...row,
|
|
2270
|
+
outputs: row.outputs.map((output, outputIndex) => {
|
|
2271
|
+
if (!output || !locationsToStrip.has(`${rowIndex}:${outputIndex}`)) return output;
|
|
2272
|
+
return {
|
|
2273
|
+
...output,
|
|
2274
|
+
prompt: ""
|
|
2275
|
+
};
|
|
2276
|
+
})
|
|
2277
|
+
}))
|
|
2278
|
+
}
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
function getEvalTablePromptStrippedPayload(payload, promptLocations, promptCountToStrip) {
|
|
2282
|
+
return stripEvalTableOutputPrompts(payload, new Set(promptLocations.slice(0, promptCountToStrip).map(({ rowIndex, outputIndex }) => `${rowIndex}:${outputIndex}`)));
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Merges comparison tables with the main table for side-by-side CSV export.
|
|
2286
|
+
*
|
|
2287
|
+
* @param mainEvalId - The ID of the main evaluation
|
|
2288
|
+
* @param mainTable - The main evaluation table
|
|
2289
|
+
* @param comparisonData - Array of comparison eval data (eval ID and table)
|
|
2290
|
+
* @returns Merged table with all prompts and outputs combined
|
|
2291
|
+
*/
|
|
2292
|
+
function mergeComparisonTables(mainEvalId, mainTable, comparisonData) {
|
|
2293
|
+
return {
|
|
2294
|
+
head: {
|
|
2295
|
+
prompts: [...mainTable.head.prompts.map((prompt) => ({
|
|
2296
|
+
...prompt,
|
|
2297
|
+
label: `[${mainEvalId}] ${prompt.label || ""}`
|
|
2298
|
+
})), ...comparisonData.flatMap(({ evalId, table }) => table.head.prompts.map((prompt) => ({
|
|
2299
|
+
...prompt,
|
|
2300
|
+
label: `[${evalId}] ${prompt.label || ""}`
|
|
2301
|
+
})))],
|
|
2302
|
+
vars: mainTable.head.vars
|
|
2303
|
+
},
|
|
2304
|
+
body: mainTable.body.map((row) => {
|
|
2305
|
+
const testIdx = row.testIdx;
|
|
2306
|
+
const matchingRows = comparisonData.map(({ table }) => table.body.find((compRow) => compRow.testIdx === testIdx)).filter((r) => r !== void 0);
|
|
2307
|
+
return {
|
|
2308
|
+
...row,
|
|
2309
|
+
outputs: [...row.outputs, ...matchingRows.flatMap((r) => r.outputs)]
|
|
2310
|
+
};
|
|
2311
|
+
})
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* High-level function to generate CSV from an evaluation.
|
|
2316
|
+
*
|
|
2317
|
+
* Used by WebUI for CSV downloads (with or without comparison evals).
|
|
2318
|
+
* For CLI exports, use `streamEvalCsv` which is more memory-efficient
|
|
2319
|
+
* for large datasets.
|
|
2320
|
+
*
|
|
2321
|
+
* Both functions use the same underlying formatting (`evalTableToCsv`,
|
|
2322
|
+
* `buildCsvHeaders`, `tableRowToCsvValues`) to ensure consistent output.
|
|
2323
|
+
*
|
|
2324
|
+
* @param eval_ - The evaluation to export
|
|
2325
|
+
* @param options - Export options including filters and comparison eval IDs
|
|
2326
|
+
* @returns CSV formatted string
|
|
2327
|
+
* @throws ComparisonEvalNotFoundError if a comparison eval ID is not found
|
|
2328
|
+
* @throws Error if comparison exports requested without findEvalById callback
|
|
2329
|
+
*/
|
|
2330
|
+
async function generateEvalCsv(eval_, options = {}) {
|
|
2331
|
+
const UNLIMITED_RESULTS = Number.MAX_SAFE_INTEGER;
|
|
2332
|
+
const mainTable = await eval_.getTablePage({
|
|
2333
|
+
offset: 0,
|
|
2334
|
+
limit: UNLIMITED_RESULTS,
|
|
2335
|
+
filterMode: options.filterMode,
|
|
2336
|
+
searchQuery: options.searchQuery,
|
|
2337
|
+
filters: options.filters
|
|
2338
|
+
});
|
|
2339
|
+
let finalTable = mainTable;
|
|
2340
|
+
if (options.comparisonEvalIds && options.comparisonEvalIds.length > 0) {
|
|
2341
|
+
if (!options.findEvalById) throw new Error("findEvalById callback is required for comparison exports. Pass Eval.findById when calling from server routes.");
|
|
2342
|
+
const indices = mainTable.body.map((row) => row.testIdx);
|
|
2343
|
+
const comparisonData = await Promise.all(options.comparisonEvalIds.map(async (comparisonEvalId) => {
|
|
2344
|
+
const comparisonEval = await options.findEvalById(comparisonEvalId);
|
|
2345
|
+
if (!comparisonEval) throw new ComparisonEvalNotFoundError(comparisonEvalId);
|
|
2346
|
+
const table = await comparisonEval.getTablePage({
|
|
2347
|
+
offset: 0,
|
|
2348
|
+
limit: indices.length,
|
|
2349
|
+
filterMode: "all",
|
|
2350
|
+
testIndices: indices,
|
|
2351
|
+
searchQuery: options.searchQuery,
|
|
2352
|
+
filters: options.filters
|
|
2353
|
+
});
|
|
2354
|
+
return {
|
|
2355
|
+
evalId: comparisonEval.id,
|
|
2356
|
+
table
|
|
2357
|
+
};
|
|
2358
|
+
}));
|
|
2359
|
+
finalTable = mergeComparisonTables(eval_.id, mainTable, comparisonData);
|
|
2360
|
+
}
|
|
2361
|
+
return evalTableToCsv(finalTable, {
|
|
2362
|
+
env: eval_.config.env,
|
|
2363
|
+
isRedteam: Boolean(eval_.config.redteam)
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Stream CSV data from an evaluation in batches.
|
|
2368
|
+
*
|
|
2369
|
+
* This is more memory-efficient for large evaluations as it processes
|
|
2370
|
+
* results in batches rather than loading everything into memory.
|
|
2371
|
+
*
|
|
2372
|
+
* Used by the CLI export (`promptfoo eval -o output.csv`) to maintain
|
|
2373
|
+
* consistent CSV format with WebUI exports while handling large datasets.
|
|
2374
|
+
*
|
|
2375
|
+
* @param eval_ - The evaluation to export
|
|
2376
|
+
* @param options - Streaming options including the write callback
|
|
2377
|
+
*/
|
|
2378
|
+
async function streamEvalCsv(eval_, options) {
|
|
2379
|
+
const { isRedteam = false, write } = options;
|
|
2380
|
+
const env = eval_.config?.env;
|
|
2381
|
+
const varNames = eval_.vars;
|
|
2382
|
+
const prompts = eval_.prompts;
|
|
2383
|
+
const numPrompts = prompts.length;
|
|
2384
|
+
const namedScoreNamesByPrompt = collectNamedScoreNamesByPromptFromAggregate(prompts);
|
|
2385
|
+
const fallbackPromptIndices = prompts.flatMap((prompt, promptIndex) => promptNeedsDiscoveryFallback(prompt) ? [promptIndex] : []);
|
|
2386
|
+
const fallbackPromptIndexSet = new Set(fallbackPromptIndices);
|
|
2387
|
+
const accumulator = {
|
|
2388
|
+
metricNamesByPrompt: /* @__PURE__ */ new Map(),
|
|
2389
|
+
hasDescriptions: false
|
|
2390
|
+
};
|
|
2391
|
+
for await (const batchResults of eval_.fetchResultsBatched()) scanBatchForDiscovery(batchResults, accumulator, fallbackPromptIndexSet);
|
|
2392
|
+
const hasDescriptions = accumulator.hasDescriptions;
|
|
2393
|
+
for (const promptIndex of fallbackPromptIndices) {
|
|
2394
|
+
const names = accumulator.metricNamesByPrompt.get(promptIndex);
|
|
2395
|
+
namedScoreNamesByPrompt[promptIndex] = names ? [...names].sort((a, b) => a.localeCompare(b)) : [];
|
|
2396
|
+
}
|
|
2397
|
+
const headers = buildCsvHeaders(varNames, prompts, {
|
|
2398
|
+
hasDescriptions,
|
|
2399
|
+
isRedteam,
|
|
2400
|
+
namedScoreNamesByPrompt
|
|
2401
|
+
});
|
|
2402
|
+
await write(stringify(escapeCsvRowsForFormulaInjection([headers], env)));
|
|
2403
|
+
for await (const batchResults of eval_.fetchResultsBatched()) {
|
|
2404
|
+
const csvRows = batchToStreamRows(batchResults, varNames, numPrompts).map((row) => tableRowToCsvValues(row, {
|
|
2405
|
+
hasDescriptions,
|
|
2406
|
+
isRedteam,
|
|
2407
|
+
namedScoreNamesByPrompt
|
|
2408
|
+
}));
|
|
2409
|
+
if (csvRows.length > 0) await write(stringify(escapeCsvRowsForFormulaInjection(csvRows, env)));
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
//#endregion
|
|
2413
|
+
//#region src/util/junit.ts
|
|
2414
|
+
const MAX_JUNIT_NAME_LENGTH = 512;
|
|
2415
|
+
const MAX_JUNIT_DETAIL_LENGTH = 8192;
|
|
2416
|
+
const JUNIT_ASSERTION_FAILURE_MESSAGE = "Assertion failed";
|
|
2417
|
+
const JUNIT_EVALUATION_ERROR_MESSAGE = "Evaluation error";
|
|
2418
|
+
const INVALID_XML_CHARACTERS = /[^\t\n\r\u0020-\ud7ff\ue000-\ufffd\u{10000}-\u{10ffff}]/gu;
|
|
2419
|
+
function truncateText(value, maxLength) {
|
|
2420
|
+
if (value.length <= maxLength) return value;
|
|
2421
|
+
const cutoff = maxLength - 3;
|
|
2422
|
+
const end = (value.codePointAt(cutoff - 1) ?? 0) > 65535 ? cutoff + 1 : cutoff;
|
|
2423
|
+
return `${value.slice(0, end)}...`;
|
|
2424
|
+
}
|
|
2425
|
+
function normalizeInlineText(value, fallback, maxLength = MAX_JUNIT_NAME_LENGTH) {
|
|
2426
|
+
return truncateText((value ?? "").replace(/\s+/g, " ").replace(INVALID_XML_CHARACTERS, "").replace(/\s+/g, " ").trim() || fallback, maxLength);
|
|
2427
|
+
}
|
|
2428
|
+
function formatDurationSeconds(durationMs) {
|
|
2429
|
+
return Number(((typeof durationMs === "number" && Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0) / 1e3).toFixed(3)).toString();
|
|
2430
|
+
}
|
|
2431
|
+
function getEvaluationTimestamp(evalRecord) {
|
|
2432
|
+
const date = new Date(evalRecord.createdAt);
|
|
2433
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
2434
|
+
}
|
|
2435
|
+
function getTestCaseName(result) {
|
|
2436
|
+
const prefix = `test ${result.testIdx + 1}`;
|
|
2437
|
+
const description = normalizeInlineText(result.description || result.testCase.description, "");
|
|
2438
|
+
return description ? `${prefix}: ${description}` : prefix;
|
|
2439
|
+
}
|
|
2440
|
+
function getFailedComponentResults(gradingResult) {
|
|
2441
|
+
return gradingResult?.componentResults?.filter((component) => !component.pass) ?? [];
|
|
2442
|
+
}
|
|
2443
|
+
function getAssertionLabel(gradingResult) {
|
|
2444
|
+
return gradingResult.assertion?.type ?? "assertion";
|
|
2445
|
+
}
|
|
2446
|
+
function getFailedAssertionLabels(gradingResult) {
|
|
2447
|
+
const failedComponents = getFailedComponentResults(gradingResult);
|
|
2448
|
+
if (failedComponents.length > 0) return failedComponents.map(getAssertionLabel);
|
|
2449
|
+
return gradingResult ? [getAssertionLabel(gradingResult)] : [];
|
|
2450
|
+
}
|
|
2451
|
+
function getFailureDetails(result) {
|
|
2452
|
+
const lines = [`Score: ${result.score}`, `Reason: ${JUNIT_ASSERTION_FAILURE_MESSAGE}`];
|
|
2453
|
+
const failedAssertionLabels = getFailedAssertionLabels(result.gradingResult);
|
|
2454
|
+
if (failedAssertionLabels.length > 0) {
|
|
2455
|
+
lines.push("Failed assertions:");
|
|
2456
|
+
for (const label of failedAssertionLabels) lines.push(`- ${label}`);
|
|
2457
|
+
}
|
|
2458
|
+
return truncateText(lines.join("\n").replace(INVALID_XML_CHARACTERS, ""), MAX_JUNIT_DETAIL_LENGTH);
|
|
2459
|
+
}
|
|
2460
|
+
function projectEvalResult(result) {
|
|
2461
|
+
const projected = "toEvaluateResult" in result ? result.toEvaluateResult() : result;
|
|
2462
|
+
return {
|
|
2463
|
+
description: projected.description,
|
|
2464
|
+
error: projected.error,
|
|
2465
|
+
failureReason: projected.failureReason,
|
|
2466
|
+
gradingResult: projected.gradingResult,
|
|
2467
|
+
latencyMs: projected.latencyMs,
|
|
2468
|
+
prompt: projected.prompt,
|
|
2469
|
+
promptId: projected.promptId,
|
|
2470
|
+
promptIdx: projected.promptIdx,
|
|
2471
|
+
provider: projected.provider,
|
|
2472
|
+
score: projected.score,
|
|
2473
|
+
success: projected.success,
|
|
2474
|
+
testCase: projected.testCase,
|
|
2475
|
+
testIdx: projected.testIdx
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
async function* iterateJunitProjectedResults(evalRecord) {
|
|
2479
|
+
if (evalRecord.useOldResults()) {
|
|
2480
|
+
const results = await evalRecord.getResults();
|
|
2481
|
+
for (const result of results) yield projectEvalResult(result);
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
if (!evalRecord.persisted) {
|
|
2485
|
+
for (const result of evalRecord.results) yield projectEvalResult(result);
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
for await (const batchResults of evalRecord.fetchResultsBatched()) for (const result of batchResults) yield projectEvalResult(result);
|
|
2489
|
+
}
|
|
2490
|
+
async function buildJunitSuites(evalRecord) {
|
|
2491
|
+
const suites = /* @__PURE__ */ new Map();
|
|
2492
|
+
const providersByDisplayName = /* @__PURE__ */ new Map();
|
|
2493
|
+
for await (const result of iterateJunitProjectedResults(evalRecord)) {
|
|
2494
|
+
const { provider } = result;
|
|
2495
|
+
const providerKey = JSON.stringify([provider.id ?? "", provider.label ?? ""]);
|
|
2496
|
+
const promptKey = result.promptId || `prompt-index:${result.promptIdx}`;
|
|
2497
|
+
const key = JSON.stringify([providerKey, promptKey]);
|
|
2498
|
+
let suite = suites.get(key);
|
|
2499
|
+
if (!suite) {
|
|
2500
|
+
const rawName = provider.label || provider.id || "";
|
|
2501
|
+
const displayName = normalizeInlineText(rawName, "unknown provider");
|
|
2502
|
+
const providers = providersByDisplayName.get(displayName) ?? /* @__PURE__ */ new Set();
|
|
2503
|
+
providers.add(providerKey);
|
|
2504
|
+
providersByDisplayName.set(displayName, providers);
|
|
2505
|
+
suite = {
|
|
2506
|
+
providerKey,
|
|
2507
|
+
rawName,
|
|
2508
|
+
promptKey,
|
|
2509
|
+
promptIdx: result.promptIdx,
|
|
2510
|
+
displayName: "",
|
|
2511
|
+
errors: 0,
|
|
2512
|
+
failures: 0,
|
|
2513
|
+
testcases: [],
|
|
2514
|
+
timeMs: 0
|
|
2515
|
+
};
|
|
2516
|
+
suites.set(key, suite);
|
|
2517
|
+
}
|
|
2518
|
+
suite.promptIdx = Math.min(suite.promptIdx, result.promptIdx);
|
|
2519
|
+
suite.testcases.push({
|
|
2520
|
+
testcase: buildJunitTestCase(result),
|
|
2521
|
+
testIdx: result.testIdx
|
|
2522
|
+
});
|
|
2523
|
+
suite.timeMs += result.latencyMs;
|
|
2524
|
+
if (!result.success) {
|
|
2525
|
+
if (result.failureReason === ResultFailureReason.ASSERT) suite.failures += 1;
|
|
2526
|
+
else suite.errors += 1;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
const promptCountsByProvider = /* @__PURE__ */ new Map();
|
|
2530
|
+
const orderedSuites = [...suites.values()].sort((a, b) => a.promptIdx - b.promptIdx || a.promptKey.localeCompare(b.promptKey));
|
|
2531
|
+
for (const suite of orderedSuites) {
|
|
2532
|
+
const baseName = normalizeInlineText(suite.rawName, "unknown provider");
|
|
2533
|
+
const suffix = (providersByDisplayName.get(baseName)?.size ?? 0) > 1 || suite.rawName.search(INVALID_XML_CHARACTERS) !== -1 ? ` (${sha256(suite.providerKey).slice(0, 16)})` : "";
|
|
2534
|
+
const providerName = normalizeInlineText(suite.rawName, "unknown provider", MAX_JUNIT_NAME_LENGTH - suffix.length);
|
|
2535
|
+
const ordinal = (promptCountsByProvider.get(suite.providerKey) ?? 0) + 1;
|
|
2536
|
+
promptCountsByProvider.set(suite.providerKey, ordinal);
|
|
2537
|
+
suite.displayName = `[${providerName}] prompt ${ordinal}${suffix}`;
|
|
2538
|
+
for (const { testcase } of suite.testcases) testcase["@_classname"] = suite.displayName;
|
|
2539
|
+
}
|
|
2540
|
+
return [...suites.values()];
|
|
2541
|
+
}
|
|
2542
|
+
function buildJunitTestCase(result) {
|
|
2543
|
+
const testcase = {
|
|
2544
|
+
"@_name": getTestCaseName(result),
|
|
2545
|
+
"@_time": formatDurationSeconds(result.latencyMs)
|
|
2546
|
+
};
|
|
2547
|
+
if (!result.success) {
|
|
2548
|
+
if (result.failureReason === ResultFailureReason.ASSERT) testcase.failure = {
|
|
2549
|
+
"#text": getFailureDetails(result),
|
|
2550
|
+
"@_message": JUNIT_ASSERTION_FAILURE_MESSAGE,
|
|
2551
|
+
"@_type": "assertion"
|
|
2552
|
+
};
|
|
2553
|
+
else testcase.error = {
|
|
2554
|
+
"#text": `Reason: ${JUNIT_EVALUATION_ERROR_MESSAGE}`,
|
|
2555
|
+
"@_message": JUNIT_EVALUATION_ERROR_MESSAGE,
|
|
2556
|
+
"@_type": "error"
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
return testcase;
|
|
2560
|
+
}
|
|
2561
|
+
async function createJunitXml(evalRecord) {
|
|
2562
|
+
const suites = await buildJunitSuites(evalRecord);
|
|
2563
|
+
const tests = suites.reduce((sum, suite) => sum + suite.testcases.length, 0);
|
|
2564
|
+
const failures = suites.reduce((sum, suite) => sum + suite.failures, 0);
|
|
2565
|
+
const errors = suites.reduce((sum, suite) => sum + suite.errors, 0);
|
|
2566
|
+
const totalTimeMs = suites.reduce((sum, suite) => sum + suite.timeMs, 0);
|
|
2567
|
+
const timestamp = getEvaluationTimestamp(evalRecord);
|
|
2568
|
+
return new XMLBuilder({
|
|
2569
|
+
format: true,
|
|
2570
|
+
ignoreAttributes: false,
|
|
2571
|
+
indentBy: " "
|
|
2572
|
+
}).build({
|
|
2573
|
+
"?xml": {
|
|
2574
|
+
"@_version": "1.0",
|
|
2575
|
+
"@_encoding": "UTF-8"
|
|
2576
|
+
},
|
|
2577
|
+
testsuites: {
|
|
2578
|
+
"@_errors": errors,
|
|
2579
|
+
"@_failures": failures,
|
|
2580
|
+
"@_name": "promptfoo",
|
|
2581
|
+
"@_skipped": 0,
|
|
2582
|
+
"@_tests": tests,
|
|
2583
|
+
"@_time": formatDurationSeconds(totalTimeMs),
|
|
2584
|
+
testsuite: suites.map((suite) => ({
|
|
2585
|
+
"@_errors": suite.errors,
|
|
2586
|
+
"@_failures": suite.failures,
|
|
2587
|
+
"@_name": suite.displayName,
|
|
2588
|
+
"@_skipped": 0,
|
|
2589
|
+
"@_tests": suite.testcases.length,
|
|
2590
|
+
"@_time": formatDurationSeconds(suite.timeMs),
|
|
2591
|
+
...timestamp ? { "@_timestamp": timestamp } : {},
|
|
2592
|
+
testcase: suite.testcases.sort((a, b) => a.testIdx - b.testIdx).map(({ testcase }) => testcase)
|
|
2593
|
+
}))
|
|
2594
|
+
}
|
|
2595
|
+
}).replace(INVALID_XML_CHARACTERS, "");
|
|
2596
|
+
}
|
|
2597
|
+
async function writeJunitXmlOutput(outputPath, evalRecord) {
|
|
2598
|
+
await fsPromises.writeFile(outputPath, await createJunitXml(evalRecord));
|
|
2599
|
+
}
|
|
2600
|
+
const SUPPORTED_OUTPUT_FILE_FORMATS = [...OutputFileExtension.options, "junit.xml"];
|
|
2601
|
+
function getOutputFileFormat(outputPath) {
|
|
2602
|
+
if (outputPath.toLowerCase().endsWith(".junit.xml")) return "junit.xml";
|
|
2603
|
+
return OutputFileExtension.safeParse(path$1.extname(outputPath).slice(1).toLowerCase()).data;
|
|
2604
|
+
}
|
|
2605
|
+
//#endregion
|
|
2606
|
+
//#region src/util/output.ts
|
|
2607
|
+
function warnOnDegradedJsonlRecovery(evalRecord, outputPaths) {
|
|
2608
|
+
if (!evalRecord.resultPersistenceFailed) return;
|
|
2609
|
+
if (outputPaths.some((outputPath) => getOutputFileFormat(outputPath) === "jsonl")) logger.warn("[Output] Reconciling JSONL from streamed rows and the eval record because one or more rows failed to persist");
|
|
2610
|
+
}
|
|
2611
|
+
function isFileNotFoundError(error) {
|
|
2612
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
2613
|
+
}
|
|
2614
|
+
function isPermissionDeniedError(error) {
|
|
2615
|
+
return error !== null && typeof error === "object" && "code" in error && (error.code === "EACCES" || error.code === "EPERM");
|
|
2616
|
+
}
|
|
2617
|
+
async function resolveJsonlOutputPath(outputPath) {
|
|
2618
|
+
try {
|
|
2619
|
+
if (!(await fsPromises.lstat(outputPath)).isSymbolicLink()) return outputPath;
|
|
2620
|
+
try {
|
|
2621
|
+
return await fsPromises.realpath(outputPath);
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
if (isFileNotFoundError(error)) return path$1.resolve(path$1.dirname(outputPath), await fsPromises.readlink(outputPath));
|
|
2624
|
+
throw error;
|
|
2625
|
+
}
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
if (isFileNotFoundError(error)) return outputPath;
|
|
2628
|
+
throw error;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
async function appendJsonlResultBatch(outputPath, results, stripFlags) {
|
|
2632
|
+
if (results.length === 0) return;
|
|
2633
|
+
const text = results.map((result) => JSON.stringify(sanitizeResultForJsonlArtifact(result, stripFlags))).join(os$1.EOL) + os$1.EOL;
|
|
2634
|
+
await fsPromises.appendFile(outputPath, text);
|
|
2635
|
+
}
|
|
2636
|
+
async function readStreamedJsonlResults(outputPath) {
|
|
2637
|
+
let contents;
|
|
2638
|
+
try {
|
|
2639
|
+
contents = await fsPromises.readFile(outputPath, "utf8");
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
if (error.code === "ENOENT") return [];
|
|
2642
|
+
throw error;
|
|
2643
|
+
}
|
|
2644
|
+
const results = [];
|
|
2645
|
+
const lines = contents.split(/\r?\n/).filter(Boolean);
|
|
2646
|
+
for (const [index, line] of lines.entries()) try {
|
|
2647
|
+
results.push(JSON.parse(line));
|
|
2648
|
+
} catch (error) {
|
|
2649
|
+
logger.warn(`[Output] Skipping malformed streamed JSONL row at ${outputPath}:${index + 1} during recovery: ${error instanceof Error ? error.message : String(error)}`);
|
|
2650
|
+
}
|
|
2651
|
+
return results;
|
|
2652
|
+
}
|
|
2653
|
+
async function collectJsonlResultsAfterPersistenceFailure(outputPath, evalRecord) {
|
|
2654
|
+
const stripFlags = getStripFlags(evalRecord.config.env);
|
|
2655
|
+
const finalResults = /* @__PURE__ */ new Map();
|
|
2656
|
+
const put = (result) => finalResults.set(getResultIndexKey(result), result);
|
|
2657
|
+
for (const result of await readStreamedJsonlResults(outputPath)) put(result);
|
|
2658
|
+
for await (const batchResults of evalRecord.fetchResultsBatched()) for (const result of batchResults) {
|
|
2659
|
+
const evaluateResult = asEvaluateResult(result, stripFlags);
|
|
2660
|
+
if (!evalRecord.hasResultPersistenceFailure(evaluateResult)) put(evaluateResult);
|
|
2661
|
+
}
|
|
2662
|
+
for (const result of evalRecord.getFinalJsonlResults()) put(result);
|
|
2663
|
+
return Array.from(finalResults.values());
|
|
2664
|
+
}
|
|
2665
|
+
async function getExistingFileMode(outputPath) {
|
|
2666
|
+
try {
|
|
2667
|
+
return (await fsPromises.stat(outputPath)).mode & 4095;
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
if (isFileNotFoundError(error)) return;
|
|
2670
|
+
throw error;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
async function appendJsonlResults(outputPath, evalRecord, recoveredResults) {
|
|
2674
|
+
const stripFlags = getStripFlags(evalRecord.config.env);
|
|
2675
|
+
if (recoveredResults) {
|
|
2676
|
+
await appendJsonlResultBatch(outputPath, recoveredResults, stripFlags);
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
for await (const batchResults of evalRecord.fetchResultsBatched()) await appendJsonlResultBatch(outputPath, batchResults.map((result) => asEvaluateResult(result, stripFlags)), stripFlags);
|
|
2680
|
+
}
|
|
2681
|
+
async function rewriteJsonlWithExternalBackup(outputPath, outputMode, evalRecord, preparedReplacementPath, recoveredResults) {
|
|
2682
|
+
const tempDirectory = await fsPromises.mkdtemp(path$1.join(os$1.tmpdir(), "promptfoo-jsonl-"));
|
|
2683
|
+
const backupPath = path$1.join(tempDirectory, "backup.jsonl");
|
|
2684
|
+
const replacementPath = path$1.join(tempDirectory, "replacement.jsonl");
|
|
2685
|
+
let overwriteAttempted = false;
|
|
2686
|
+
let preserveTempDirectory = false;
|
|
2687
|
+
let hasBackup = false;
|
|
2688
|
+
try {
|
|
2689
|
+
try {
|
|
2690
|
+
await fsPromises.copyFile(outputPath, backupPath);
|
|
2691
|
+
hasBackup = true;
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
if (!isFileNotFoundError(error)) throw error;
|
|
2694
|
+
}
|
|
2695
|
+
if (preparedReplacementPath) await fsPromises.copyFile(preparedReplacementPath, replacementPath);
|
|
2696
|
+
else {
|
|
2697
|
+
if (outputMode === void 0) await fsPromises.writeFile(replacementPath, "");
|
|
2698
|
+
else await fsPromises.writeFile(replacementPath, "", { mode: outputMode });
|
|
2699
|
+
await appendJsonlResults(replacementPath, evalRecord, recoveredResults);
|
|
2700
|
+
}
|
|
2701
|
+
overwriteAttempted = true;
|
|
2702
|
+
await fsPromises.copyFile(replacementPath, outputPath);
|
|
2703
|
+
if (outputMode !== void 0) await fsPromises.chmod(outputPath, outputMode);
|
|
2704
|
+
} catch (error) {
|
|
2705
|
+
if (overwriteAttempted && hasBackup) try {
|
|
2706
|
+
await fsPromises.copyFile(backupPath, outputPath);
|
|
2707
|
+
} catch (restoreError) {
|
|
2708
|
+
preserveTempDirectory = true;
|
|
2709
|
+
logger.error("[Output] Failed to restore JSONL output after rewrite failure", {
|
|
2710
|
+
backupPath,
|
|
2711
|
+
error: restoreError
|
|
2712
|
+
});
|
|
2713
|
+
throw new Error(`Failed to rewrite JSONL output (${error instanceof Error ? error.message : String(error)}) and restore backup (${restoreError instanceof Error ? restoreError.message : String(restoreError)}). Backup retained at ${backupPath}`);
|
|
2714
|
+
}
|
|
2715
|
+
throw error;
|
|
2716
|
+
} finally {
|
|
2717
|
+
if (!preserveTempDirectory) await fsPromises.rm(tempDirectory, {
|
|
2718
|
+
recursive: true,
|
|
2719
|
+
force: true
|
|
2720
|
+
}).catch((error) => {
|
|
2721
|
+
logger.warn("[Output] Failed to remove temporary JSONL backup directory", {
|
|
2722
|
+
error,
|
|
2723
|
+
tempDirectory
|
|
2724
|
+
});
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
async function removeTemporaryJsonlOutput(tempOutputPath) {
|
|
2729
|
+
await fsPromises.rm(tempOutputPath, { force: true }).catch((error) => {
|
|
2730
|
+
logger.warn("[Output] Failed to remove temporary JSONL output", {
|
|
2731
|
+
error,
|
|
2732
|
+
tempOutputPath
|
|
2733
|
+
});
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
const outputToSimpleString = (output) => {
|
|
2737
|
+
const passFailText = output.pass ? "[PASS]" : output.failureReason === ResultFailureReason.ASSERT ? "[FAIL]" : "[ERROR]";
|
|
2738
|
+
const namedScoresText = Object.entries(output.namedScores).map(([name, value]) => `${name}: ${value?.toFixed(2)}`).join(", ");
|
|
2739
|
+
const scoreText = namedScoresText.length > 0 ? `(${output.score?.toFixed(2)}, ${namedScoresText})` : `(${output.score?.toFixed(2)})`;
|
|
2740
|
+
const gradingResultText = output.gradingResult ? `${output.pass ? "Pass" : "Fail"} Reason: ${output.gradingResult.reason}` : "";
|
|
2741
|
+
return dedent`
|
|
2742
|
+
${passFailText} ${scoreText}
|
|
2743
|
+
|
|
2744
|
+
${output.text}
|
|
2745
|
+
|
|
2746
|
+
${gradingResultText}
|
|
2747
|
+
`.trim();
|
|
2748
|
+
};
|
|
2749
|
+
const outputToHtmlReportCell = (output) => {
|
|
2750
|
+
const status = output.pass ? "pass" : output.failureReason === ResultFailureReason.ERROR ? "error" : "fail";
|
|
2751
|
+
const namedScores = Object.entries(output.namedScores).map(([name, value]) => ({
|
|
2752
|
+
name,
|
|
2753
|
+
value: value?.toFixed(2)
|
|
2754
|
+
}));
|
|
2755
|
+
return {
|
|
2756
|
+
status,
|
|
2757
|
+
statusLabel: status.toUpperCase(),
|
|
2758
|
+
score: output.score?.toFixed(2),
|
|
2759
|
+
namedScores,
|
|
2760
|
+
text: output.text,
|
|
2761
|
+
reason: output.gradingResult?.reason || "",
|
|
2762
|
+
prompt: output.prompt,
|
|
2763
|
+
provider: output.provider || "",
|
|
2764
|
+
error: output.error || "",
|
|
2765
|
+
failureReason: output.failureReason,
|
|
2766
|
+
latencyDisplay: Number.isFinite(output.latencyMs) ? `${output.latencyMs.toFixed(0)} ms` : "",
|
|
2767
|
+
costDisplay: Number.isFinite(output.cost) && output.cost > 0 ? `$${output.cost.toFixed(6)}` : "",
|
|
2768
|
+
totalTokensDisplay: typeof output.tokenUsage?.total === "number" ? output.tokenUsage.total.toLocaleString("en-US") : "",
|
|
2769
|
+
promptTokensDisplay: typeof output.tokenUsage?.prompt === "number" ? output.tokenUsage.prompt.toLocaleString("en-US") : "",
|
|
2770
|
+
completionTokensDisplay: typeof output.tokenUsage?.completion === "number" ? output.tokenUsage.completion.toLocaleString("en-US") : ""
|
|
2771
|
+
};
|
|
2772
|
+
};
|
|
2773
|
+
async function createOutputSummary(evalRecord, stripFlags) {
|
|
2774
|
+
const summary = await evalRecord.toEvaluateSummary();
|
|
2775
|
+
const prompts = ("prompts" in summary ? summary.prompts : summary.table.head.prompts).map((prompt) => prompt.config ? {
|
|
2776
|
+
...prompt,
|
|
2777
|
+
config: sanitizeConfigForOutput(prompt.config, stripFlags)
|
|
2778
|
+
} : prompt);
|
|
2779
|
+
return "prompts" in summary ? {
|
|
2780
|
+
...summary,
|
|
2781
|
+
prompts
|
|
2782
|
+
} : {
|
|
2783
|
+
...summary,
|
|
2784
|
+
table: {
|
|
2785
|
+
...summary.table,
|
|
2786
|
+
head: {
|
|
2787
|
+
...summary.table.head,
|
|
2788
|
+
prompts
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
function resultsForMediaExportScan(results, shouldStripResponseOutput) {
|
|
2794
|
+
if (!shouldStripResponseOutput) return results;
|
|
2795
|
+
return {
|
|
2796
|
+
...results,
|
|
2797
|
+
results: results.results.map((result) => {
|
|
2798
|
+
const response = result.response;
|
|
2799
|
+
if (!response?.metadata || !("blobUris" in response.metadata)) return result;
|
|
2800
|
+
const { metadata: responseMetadata, ...projectedResponse } = response;
|
|
2801
|
+
const { blobUris: _blobUris, ...metadata } = responseMetadata;
|
|
2802
|
+
return {
|
|
2803
|
+
...result,
|
|
2804
|
+
response: {
|
|
2805
|
+
...projectedResponse,
|
|
2806
|
+
...Object.keys(metadata).length > 0 && { metadata }
|
|
2807
|
+
}
|
|
2808
|
+
};
|
|
2809
|
+
})
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
function createOutputMetadata(evalRecord) {
|
|
2813
|
+
let evaluationCreatedAt;
|
|
2814
|
+
if (evalRecord.createdAt) try {
|
|
2815
|
+
const date = new Date(evalRecord.createdAt);
|
|
2816
|
+
evaluationCreatedAt = Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
2817
|
+
} catch {
|
|
2818
|
+
evaluationCreatedAt = void 0;
|
|
2819
|
+
}
|
|
2820
|
+
return {
|
|
2821
|
+
promptfooVersion: VERSION,
|
|
2822
|
+
nodeVersion: process.version,
|
|
2823
|
+
platform: os$1.platform(),
|
|
2824
|
+
arch: os$1.arch(),
|
|
2825
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2826
|
+
evaluationCreatedAt,
|
|
2827
|
+
author: evalRecord.author ?? void 0
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
async function createOutputData(evalRecord, shareableUrl, options = {}) {
|
|
2831
|
+
const stripFlags = getStripFlags(evalRecord.config.env);
|
|
2832
|
+
const summary = await createOutputSummary(evalRecord, stripFlags);
|
|
2833
|
+
const redactedConfig = sanitizeConfigForOutput(evalRecord.config, stripFlags);
|
|
2834
|
+
let traces;
|
|
2835
|
+
try {
|
|
2836
|
+
const { getTraceStore } = await import("./store-zGP98TMs.js").then((n) => n.n);
|
|
2837
|
+
traces = await getTraceStore().getTracesByEvaluation(evalRecord.id);
|
|
2838
|
+
} catch (error) {
|
|
2839
|
+
logger.warn(`Failed to fetch traces for output ${evalRecord.id}; traces omitted from export: ${error}`);
|
|
2840
|
+
}
|
|
2841
|
+
const output = {
|
|
2842
|
+
evalId: evalRecord.id,
|
|
2843
|
+
results: summary,
|
|
2844
|
+
config: redactedConfig,
|
|
2845
|
+
shareableUrl,
|
|
2846
|
+
metadata: createOutputMetadata(evalRecord),
|
|
2847
|
+
...evalRecord.vars?.length > 0 && { vars: [...evalRecord.vars] },
|
|
2848
|
+
...evalRecord.runtimeOptions && { runtimeOptions: sanitizeRuntimeOptions(evalRecord.runtimeOptions) },
|
|
2849
|
+
...traces && traces.length > 0 && { traces: projectTracesForOutput(traces, stripFlags) }
|
|
2850
|
+
};
|
|
2851
|
+
if (options.includeMedia) {
|
|
2852
|
+
const blobAssets = await exportBlobAssets(evalRecord.id, summary, stripFlags.shouldStripResponseOutput, output.traces);
|
|
2853
|
+
if (blobAssets.length > 0) output.blobAssets = blobAssets;
|
|
2854
|
+
}
|
|
2855
|
+
return output;
|
|
2856
|
+
}
|
|
2857
|
+
async function exportBlobAssets(evalId, results, shouldStripResponseOutput, traces) {
|
|
2858
|
+
const { getShareAuthorizedBlob } = await import("./blobs-DiN8G72v.js").then((n) => n.t);
|
|
2859
|
+
const assets = [];
|
|
2860
|
+
for (const hash of collectBlobHashes({
|
|
2861
|
+
results: resultsForMediaExportScan(results, shouldStripResponseOutput),
|
|
2862
|
+
traces
|
|
2863
|
+
})) try {
|
|
2864
|
+
const blob = await getShareAuthorizedBlob(hash, evalId);
|
|
2865
|
+
if (!blob) continue;
|
|
2866
|
+
if (blob.data.length > 52428800) {
|
|
2867
|
+
logger.warn("[Output] Skipping oversized blob in eval export", {
|
|
2868
|
+
hash,
|
|
2869
|
+
sizeBytes: blob.data.length
|
|
2870
|
+
});
|
|
2871
|
+
continue;
|
|
2872
|
+
}
|
|
2873
|
+
assets.push({
|
|
2874
|
+
hash,
|
|
2875
|
+
mimeType: blob.metadata.mimeType,
|
|
2876
|
+
sizeBytes: blob.data.length,
|
|
2877
|
+
data: blob.data.toString("base64")
|
|
2878
|
+
});
|
|
2879
|
+
} catch (error) {
|
|
2880
|
+
logger.warn("[Output] Skipping missing blob in eval export", {
|
|
2881
|
+
error,
|
|
2882
|
+
hash
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
return assets;
|
|
2886
|
+
}
|
|
2887
|
+
/**
|
|
2888
|
+
* JSON writer with improved error handling for large datasets.
|
|
2889
|
+
* Provides helpful error messages when memory limits are exceeded.
|
|
2890
|
+
*/
|
|
2891
|
+
async function writeJsonOutputSafely(outputPath, evalRecord, shareableUrl, options) {
|
|
2892
|
+
try {
|
|
2893
|
+
const outputData = await createOutputData(evalRecord, shareableUrl, options);
|
|
2894
|
+
const jsonString = JSON.stringify(outputData, null, 2);
|
|
2895
|
+
await fsPromises.writeFile(outputPath, jsonString);
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
const msg = error?.message ?? "";
|
|
2898
|
+
const isStringLen = error instanceof RangeError && msg.includes("Invalid string length");
|
|
2899
|
+
const isHeapOOM = /heap out of memory|Array buffer allocation failed|ERR_STRING_TOO_LONG/i.test(msg);
|
|
2900
|
+
if (isStringLen || isHeapOOM) {
|
|
2901
|
+
const resultCount = await evalRecord.getResultsCount();
|
|
2902
|
+
logger.error(`Dataset too large for JSON export (${resultCount} results).`);
|
|
2903
|
+
throw new Error(`Dataset too large for JSON export. The evaluation has ${resultCount} results which exceeds memory limits. Consider using JSONL format instead: --output output.jsonl`);
|
|
2904
|
+
} else throw error;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
async function writeOutput(outputPath, evalRecord, shareableUrl, options = {}) {
|
|
2908
|
+
if (outputPath.match(/^https:\/\/docs\.google\.com\/spreadsheets\//)) {
|
|
2909
|
+
const table = await evalRecord.getTable();
|
|
2910
|
+
const rows = table.body.map((row) => {
|
|
2911
|
+
const csvRow = {};
|
|
2912
|
+
table.head.vars.forEach((varName, index) => {
|
|
2913
|
+
csvRow[varName] = row.vars[index];
|
|
2914
|
+
});
|
|
2915
|
+
table.head.prompts.forEach((prompt, index) => {
|
|
2916
|
+
csvRow[`[${prompt.provider}] ${prompt.label}`] = outputToSimpleString(row.outputs[index]);
|
|
2917
|
+
});
|
|
2918
|
+
return csvRow;
|
|
2919
|
+
});
|
|
2920
|
+
logger.info(`Writing ${rows.length} rows to Google Sheets...`);
|
|
2921
|
+
await writeCsvToGoogleSheet(rows, outputPath);
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
const outputExtension = getOutputFileFormat(outputPath);
|
|
2925
|
+
invariant(outputExtension, `Unsupported output file format ${path$1.extname(outputPath).slice(1).toLowerCase()}. Please use one of: ${SUPPORTED_OUTPUT_FILE_FORMATS.join(", ")}.`);
|
|
2926
|
+
const outputDir = path$1.dirname(outputPath);
|
|
2927
|
+
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
2928
|
+
if (outputExtension === "junit.xml") await writeJunitXmlOutput(outputPath, evalRecord);
|
|
2929
|
+
else if (outputExtension === "csv") {
|
|
2930
|
+
const fileHandle = await fsPromises.open(outputPath, "w");
|
|
2931
|
+
try {
|
|
2932
|
+
await streamEvalCsv(evalRecord, {
|
|
2933
|
+
isRedteam: Boolean(evalRecord.config.redteam),
|
|
2934
|
+
write: async (data) => {
|
|
2935
|
+
await fileHandle.write(data);
|
|
2936
|
+
}
|
|
2937
|
+
});
|
|
2938
|
+
} finally {
|
|
2939
|
+
await fileHandle.close();
|
|
2940
|
+
}
|
|
2941
|
+
} else if (outputExtension === "json") await writeJsonOutputSafely(outputPath, evalRecord, shareableUrl, options);
|
|
2942
|
+
else if (outputExtension === "yaml" || outputExtension === "yml" || outputExtension === "txt") await fsPromises.writeFile(outputPath, yaml.dump(await createOutputData(evalRecord, shareableUrl, options)));
|
|
2943
|
+
else if (outputExtension === "html") {
|
|
2944
|
+
const table = await evalRecord.getTable();
|
|
2945
|
+
const stripFlags = getStripFlags(evalRecord.config.env);
|
|
2946
|
+
const summary = await createOutputSummary(evalRecord, stripFlags);
|
|
2947
|
+
const redactedConfig = sanitizeConfigForOutput(evalRecord.config, stripFlags);
|
|
2948
|
+
const metadata = createOutputMetadata(evalRecord);
|
|
2949
|
+
const template = await fsPromises.readFile(path$1.join(getDirectory(), "tableOutput.html"), "utf-8");
|
|
2950
|
+
const htmlTable = [[...table.head.vars, ...table.head.prompts.map((prompt) => `[${prompt.provider}] ${prompt.label}`)], ...table.body.map((row, rowIndex) => [...row.vars.map((value, variableIndex) => ({
|
|
2951
|
+
kind: "variable",
|
|
2952
|
+
name: table.head.vars[variableIndex],
|
|
2953
|
+
text: value
|
|
2954
|
+
})), ...row.outputs.map((output, outputIndex) => ({
|
|
2955
|
+
kind: "output",
|
|
2956
|
+
detailId: `result-detail-${rowIndex}-${outputIndex}`,
|
|
2957
|
+
detailTitle: `Result detail - row ${rowIndex + 1}, prompt ${outputIndex + 1}`,
|
|
2958
|
+
description: row.description || row.test?.description || "",
|
|
2959
|
+
...outputToHtmlReportCell(output)
|
|
2960
|
+
}))])];
|
|
2961
|
+
const reportOutputs = table.body.flatMap((row) => row.outputs);
|
|
2962
|
+
const totalResults = reportOutputs.length;
|
|
2963
|
+
const successes = reportOutputs.filter((output) => output.pass).length;
|
|
2964
|
+
const errors = reportOutputs.filter((output) => !output.pass && output.failureReason === ResultFailureReason.ERROR).length;
|
|
2965
|
+
const failures = totalResults - successes - errors;
|
|
2966
|
+
const passRate = totalResults > 0 ? successes / totalResults * 100 : 0;
|
|
2967
|
+
const htmlOutput = getNunjucksEngine().renderString(template, {
|
|
2968
|
+
config: redactedConfig,
|
|
2969
|
+
table: htmlTable,
|
|
2970
|
+
results: summary,
|
|
2971
|
+
metadata,
|
|
2972
|
+
report: {
|
|
2973
|
+
totalResults,
|
|
2974
|
+
totalRows: table.body.length,
|
|
2975
|
+
promptCount: table.head.prompts.length,
|
|
2976
|
+
variableCount: table.head.vars.length,
|
|
2977
|
+
successes,
|
|
2978
|
+
failures,
|
|
2979
|
+
errors,
|
|
2980
|
+
passRateDisplay: `${passRate.toFixed(1)}%`
|
|
2981
|
+
}
|
|
2982
|
+
});
|
|
2983
|
+
await fsPromises.writeFile(outputPath, htmlOutput);
|
|
2984
|
+
} else if (outputExtension === "jsonl") {
|
|
2985
|
+
const jsonlOutputPath = await resolveJsonlOutputPath(outputPath);
|
|
2986
|
+
if (jsonlOutputPath !== outputPath) await fsPromises.mkdir(path$1.dirname(jsonlOutputPath), { recursive: true });
|
|
2987
|
+
const outputMode = await getExistingFileMode(jsonlOutputPath);
|
|
2988
|
+
const recoveredResults = evalRecord.resultPersistenceFailed ? await collectJsonlResultsAfterPersistenceFailure(jsonlOutputPath, evalRecord) : void 0;
|
|
2989
|
+
const tempOutputPath = path$1.join(path$1.dirname(jsonlOutputPath), `.promptfoo-${randomUUID()}.tmp`);
|
|
2990
|
+
try {
|
|
2991
|
+
try {
|
|
2992
|
+
await fsPromises.writeFile(tempOutputPath, "");
|
|
2993
|
+
} catch (error) {
|
|
2994
|
+
if (isPermissionDeniedError(error)) {
|
|
2995
|
+
logger.warn("[Output] Falling back to JSONL rewrite with external backup because the output directory is not writable");
|
|
2996
|
+
await rewriteJsonlWithExternalBackup(jsonlOutputPath, outputMode, evalRecord, void 0, recoveredResults);
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
throw error;
|
|
3000
|
+
}
|
|
3001
|
+
await appendJsonlResults(tempOutputPath, evalRecord, recoveredResults);
|
|
3002
|
+
if (outputMode !== void 0) await fsPromises.chmod(tempOutputPath, outputMode);
|
|
3003
|
+
try {
|
|
3004
|
+
await fsPromises.rename(tempOutputPath, jsonlOutputPath);
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
if (isPermissionDeniedError(error)) {
|
|
3007
|
+
logger.warn("[Output] Falling back to JSONL rewrite with external backup because replacing the output file is not permitted");
|
|
3008
|
+
await rewriteJsonlWithExternalBackup(jsonlOutputPath, outputMode, evalRecord, tempOutputPath);
|
|
3009
|
+
await removeTemporaryJsonlOutput(tempOutputPath);
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
throw error;
|
|
3013
|
+
}
|
|
3014
|
+
} catch (error) {
|
|
3015
|
+
await removeTemporaryJsonlOutput(tempOutputPath);
|
|
3016
|
+
throw error;
|
|
3017
|
+
}
|
|
3018
|
+
} else if (outputExtension === "xml") {
|
|
3019
|
+
const stripFlags = getStripFlags(evalRecord.config.env);
|
|
3020
|
+
const summary = await createOutputSummary(evalRecord, stripFlags);
|
|
3021
|
+
const redactedConfig = sanitizeConfigForOutput(evalRecord.config, stripFlags);
|
|
3022
|
+
const sanitizeForXml = (obj) => {
|
|
3023
|
+
if (obj === null || obj === void 0) return "";
|
|
3024
|
+
if (Array.isArray(obj)) return obj.map(sanitizeForXml);
|
|
3025
|
+
if (typeof obj === "object") {
|
|
3026
|
+
const sanitized = {};
|
|
3027
|
+
for (const [key, value] of Object.entries(obj)) sanitized[key] = sanitizeForXml(value);
|
|
3028
|
+
return sanitized;
|
|
3029
|
+
}
|
|
3030
|
+
return String(obj);
|
|
3031
|
+
};
|
|
3032
|
+
const xmlData = new XMLBuilder({
|
|
3033
|
+
ignoreAttributes: false,
|
|
3034
|
+
format: true,
|
|
3035
|
+
indentBy: " "
|
|
3036
|
+
}).build({ promptfoo: {
|
|
3037
|
+
evalId: evalRecord.id,
|
|
3038
|
+
results: sanitizeForXml(summary),
|
|
3039
|
+
config: sanitizeForXml(redactedConfig),
|
|
3040
|
+
shareableUrl: shareableUrl || ""
|
|
3041
|
+
} });
|
|
3042
|
+
await fsPromises.writeFile(outputPath, xmlData);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
async function writeMultipleOutputs(outputPaths, evalRecord, shareableUrl) {
|
|
3046
|
+
const errors = (await Promise.allSettled(outputPaths.map((outputPath) => writeOutput(outputPath, evalRecord, shareableUrl)))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
3047
|
+
if (errors.length > 0) throw Object.assign(/* @__PURE__ */ new Error("One or more output writes failed"), { errors });
|
|
3048
|
+
}
|
|
3049
|
+
//#endregion
|
|
3050
|
+
//#region src/util/runtime.ts
|
|
3051
|
+
function printBorder() {
|
|
3052
|
+
const border = "=".repeat(TERMINAL_MAX_WIDTH);
|
|
3053
|
+
logger.info(border);
|
|
3054
|
+
}
|
|
3055
|
+
//#endregion
|
|
3056
|
+
//#region src/util/dataUrl.ts
|
|
3057
|
+
/**
|
|
3058
|
+
* Check if a string is a data URL
|
|
3059
|
+
* @param value String to check
|
|
3060
|
+
* @returns true if value is a data URL (starts with "data:")
|
|
3061
|
+
*
|
|
3062
|
+
* @example
|
|
3063
|
+
* isDataUrl("data:image/jpeg;base64,/9j/...") // true
|
|
3064
|
+
* isDataUrl("/9j/4AAQSkZJRg...") // false
|
|
3065
|
+
* isDataUrl("https://example.com/image.jpg") // false
|
|
3066
|
+
*/
|
|
3067
|
+
function isDataUrl(value) {
|
|
3068
|
+
return typeof value === "string" && value.startsWith("data:") && value.length > 5;
|
|
3069
|
+
}
|
|
3070
|
+
/**
|
|
3071
|
+
* Parse a data URL into its components
|
|
3072
|
+
*
|
|
3073
|
+
* Handles data URLs with optional parameters (e.g., charset, name):
|
|
3074
|
+
* - `data:image/jpeg;base64,<data>` - Standard format
|
|
3075
|
+
* - `data:image/jpeg;charset=utf-8;base64,<data>` - With charset
|
|
3076
|
+
* - `data:image/jpeg;name=photo.jpg;base64,<data>` - With filename
|
|
3077
|
+
*
|
|
3078
|
+
* @param dataUrl Data URL string
|
|
3079
|
+
* @returns Parsed components (mimeType and base64Data) or null if invalid
|
|
3080
|
+
*
|
|
3081
|
+
* @example
|
|
3082
|
+
* parseDataUrl("data:image/jpeg;base64,/9j/...")
|
|
3083
|
+
* // { mimeType: "image/jpeg", base64Data: "/9j/..." }
|
|
3084
|
+
*
|
|
3085
|
+
* parseDataUrl("data:image/jpeg;charset=utf-8;base64,/9j/...")
|
|
3086
|
+
* // { mimeType: "image/jpeg", base64Data: "/9j/..." }
|
|
3087
|
+
*
|
|
3088
|
+
* parseDataUrl("invalid") // null
|
|
3089
|
+
*/
|
|
3090
|
+
function parseDataUrl(dataUrl) {
|
|
3091
|
+
if (!isDataUrl(dataUrl)) return null;
|
|
3092
|
+
const match = dataUrl.match(/^data:([^;,]+)(?:;[^,]*)?;base64,(.+)$/);
|
|
3093
|
+
if (!match) return null;
|
|
3094
|
+
return {
|
|
3095
|
+
mimeType: match[1].trim(),
|
|
3096
|
+
base64Data: match[2].trim()
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Extract base64 data from a data URL or return original if not a data URL
|
|
3101
|
+
* Useful for providers that expect raw base64 (Anthropic, Google)
|
|
3102
|
+
*
|
|
3103
|
+
* @param value Data URL or raw base64 string
|
|
3104
|
+
* @returns Raw base64 string (data URL prefix stripped if present)
|
|
3105
|
+
*
|
|
3106
|
+
* @example
|
|
3107
|
+
* extractBase64FromDataUrl("data:image/jpeg;base64,/9j/...")
|
|
3108
|
+
* // "/9j/..."
|
|
3109
|
+
*
|
|
3110
|
+
* extractBase64FromDataUrl("/9j/...") // "/9j/..." (unchanged)
|
|
3111
|
+
*/
|
|
3112
|
+
function extractBase64FromDataUrl(value) {
|
|
3113
|
+
const parsed = parseDataUrl(value);
|
|
3114
|
+
return parsed ? parsed.base64Data : value;
|
|
3115
|
+
}
|
|
3116
|
+
/**
|
|
3117
|
+
* Build a data URI from a MIME type and base64 data.
|
|
3118
|
+
*
|
|
3119
|
+
* @param mimeType MIME type (e.g. "image/png")
|
|
3120
|
+
* @param base64Data Raw base64-encoded data
|
|
3121
|
+
* @returns Data URI string
|
|
3122
|
+
*
|
|
3123
|
+
* @example
|
|
3124
|
+
* toDataUri("image/png", "iVBORw0KGgo...")
|
|
3125
|
+
* // "data:image/png;base64,iVBORw0KGgo..."
|
|
3126
|
+
*/
|
|
3127
|
+
function toDataUri(mimeType, base64Data) {
|
|
3128
|
+
return `data:${mimeType};base64,${base64Data}`;
|
|
3129
|
+
}
|
|
3130
|
+
//#endregion
|
|
3131
|
+
export { queryTestIndicesOptimized as $, sanitizeProviderIdForLog as A, persistRepeatMetadata as B, getProviderDescription as C, isOpenAiProvider as D, isGoogleProvider as E, EvalResult as F, stripTraceLinkageFromMetadata as G, projectPrompt as H, PROMPTFOO_METADATA_KEY as I, invalidateEvaluationCaches as J, toSerializableProviderRef as K, asEvaluateResult as L, loadProviderConfigsFromFile as M, normalizeProviderRef as N, isProviderAllowed as O, readProviderConfigFile as P, getTotalResultRowCount as Q, getResultIndexKey as R, doesProviderRefMatch as S, isAnthropicProvider as T, projectTracesForOutput as U, persistTraceMetadata as V, sanitizeResultForJsonlArtifact as W, notifyEvaluationsDeleted as X, notifyEvaluationChanged as Y, getCachedResultsCount as Z, deduplicateTestCases as _, printBorder as a, normalizeInput as at, resultIsForTestCase as b, getOutputFileFormat as c, hasUnscopedUpdate as ct, generateEvalCsv as d, setupSignalWatcher as dt, getCachedStandaloneEvals as et, getEvalTableOutputPromptLocationsBySize as f, updateEvalIds as ft, setupEnv as g, fetchCsvFromGoogleSheet as h, toDataUri as i, maybeFilePath as it, isProviderConfigFileReference as j, providerToIdentifier as k, ComparisonEvalNotFoundError as l, isAllEvalsDeleted as lt, mergeComparisonTables as m, HUMAN_ASSERTION_TYPE as mt, isDataUrl as n, setCachedStandaloneEvals as nt, warnOnDegradedJsonlRecovery as o, generateIdFromPrompt as ot, getEvalTablePromptStrippedPayload as p, FILE_METADATA_KEY as pt, invalidateEvaluationCache as q, parseDataUrl as r, hashPrompt as rt, writeMultipleOutputs as s, hasDeleteComponent as st, extractBase64FromDataUrl as t, getStandaloneEvalCacheKey as tt, evalTableToJson as u, readSignalFile as ut, extractRuntimeVars as v, getProviderIdentifier as w, checkProviderApiKeys as x, filterRuntimeVars as y, getStripFlags as z };
|
|
3132
|
+
|
|
3133
|
+
//# sourceMappingURL=dataUrl-DBMifX6C.js.map
|