diff --git a/.github/actions/install/branch-diff/action.yml b/.github/actions/install/branch-diff/action.yml index 21803f8c7c8..87a2865f308 100644 --- a/.github/actions/install/branch-diff/action.yml +++ b/.github/actions/install/branch-diff/action.yml @@ -7,7 +7,7 @@ inputs: runs: using: composite steps: - - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ~/.npm key: ${{ github.workflow }}-branch-diff-3.1.1 diff --git a/.github/actions/instrumentations/test/action.yml b/.github/actions/instrumentations/test/action.yml index c22a0f95fe6..ae958e3760f 100644 --- a/.github/actions/instrumentations/test/action.yml +++ b/.github/actions/instrumentations/test/action.yml @@ -7,7 +7,7 @@ runs: - uses: ./.github/actions/install - run: yarn test:instrumentations:ci shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:instrumentations:ci shell: bash - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 diff --git a/.github/actions/plugins/test-and-upstream/action.yml b/.github/actions/plugins/test-and-upstream/action.yml index 113346baec2..54d598b3764 100644 --- a/.github/actions/plugins/test-and-upstream/action.yml +++ b/.github/actions/plugins/test-and-upstream/action.yml @@ -10,7 +10,7 @@ runs: shell: bash - run: yarn test:plugins:upstream shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:plugins:ci shell: bash - run: yarn test:plugins:upstream diff --git a/.github/actions/plugins/test/action.yml b/.github/actions/plugins/test/action.yml index 36bb3c8daa8..71bf4735fda 100644 --- a/.github/actions/plugins/test/action.yml +++ b/.github/actions/plugins/test/action.yml @@ -8,7 +8,7 @@ runs: - uses: ./.github/actions/install - run: yarn test:plugins:ci shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:plugins:ci shell: bash - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 diff --git a/.github/actions/plugins/upstream/action.yml b/.github/actions/plugins/upstream/action.yml index 6c0d4783282..87370e5f73d 100644 --- a/.github/actions/plugins/upstream/action.yml +++ b/.github/actions/plugins/upstream/action.yml @@ -8,7 +8,7 @@ runs: - uses: ./.github/actions/install - run: yarn test:plugins:upstream shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:plugins:upstream shell: bash - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ec4d91db1b5..b9b15ac9b18 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,7 @@ updates: - "/" - "/.github/actions/*" - "/.github/actions/*/*" + - "/.github/workflows/*" schedule: interval: "weekly" groups: diff --git a/.github/workflows/apm-capabilities.yml b/.github/workflows/apm-capabilities.yml index 7b898dae897..3079c321b55 100644 --- a/.github/workflows/apm-capabilities.yml +++ b/.github/workflows/apm-capabilities.yml @@ -19,7 +19,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:trace:core:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -43,7 +43,7 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install with: cache: 'true' diff --git a/.github/workflows/apm-integrations.yml b/.github/workflows/apm-integrations.yml index e942d051da9..ddc030076a7 100644 --- a/.github/workflows/apm-integrations.yml +++ b/.github/workflows/apm-integrations.yml @@ -26,7 +26,7 @@ jobs: test-image: [ubuntu-22.04] include: - node-version: 18 - range: '>=5.2.0' + range: '>=5.2.0 <6.3.0' range_clean: gte.5.2.0 aerospike-image: ce-6.4.0.3 test-image: ubuntu-latest @@ -50,7 +50,7 @@ jobs: aerospike: image: aerospike:${{ matrix.aerospike-image }} ports: - - "127.0.0.1:3000-3002:3000-3002" + - '127.0.0.1:3000-3002:3000-3002' env: PLUGINS: aerospike SERVICES: aerospike @@ -166,15 +166,18 @@ jobs: - run: yarn test:plugins:ci - uses: ./.github/actions/node/latest - run: yarn test:plugins:ci - env: - OPTIONS_OVERRIDE: 1 - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 confluentinc-kafka-javascript: strategy: matrix: # using node versions matrix since this plugin testing fails due to install differences between node versions - node-version: ['18', '20', '22'] + node-version: [18, 20, 22] + range: ['>=1.0.0'] + include: + - node-version: 24 + range: '>=1.4.0' + range_clean: gte.1.4.0 runs-on: ubuntu-latest services: kafka: @@ -197,6 +200,7 @@ jobs: env: PLUGINS: confluentinc-kafka-javascript SERVICES: kafka + PACKAGE_VERSION_RANGE: ${{ matrix.range }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/testagent/start @@ -287,7 +291,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:plugins:ci - if: always() @@ -302,7 +306,10 @@ jobs: PLUGINS: express steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test + - uses: ./.github/actions/testagent/start + - uses: ./.github/actions/node/active-lts # TODO: change this to latest once we figure out the `fresh` bug + - uses: ./.github/actions/install + - run: yarn test:plugins:ci fastify: runs-on: ubuntu-latest @@ -624,7 +631,15 @@ jobs: version: - 18 - latest - range: ['>=10.2.0 <11', '>=11.0.0 <13', '11.1.4', '>=13.0.0 <14', '13.2.0', '>=14.0.0 <=14.2.6', '>=14.2.7 <15', '>=15.0.0 <15.4.1'] + range: + - '>=10.2.0 <11' + - '>=11.0.0 <13' + - '11.1.4' + - '>=13.0.0 <14' + - '13.2.0' + - '>=14.0.0 <=14.2.6' + - '>=14.2.7 <15' + - '>=15.0.0 <15.4.1' include: - range: '>=10.2.0 <11' range_clean: gte.10.2.0.and.lt.11 @@ -649,7 +664,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:plugins:ci - if: always() @@ -733,7 +748,7 @@ jobs: - uses: ./.github/actions/node/newest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:plugins:ci # - run: yarn test:plugins:upstream - if: always() @@ -825,13 +840,20 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/plugins/test + # Restify isn't compatible with Node.js v24 so we don't run against latest Node.js + # see: https://github.com/restify/node-restify/issues/1984 restify: runs-on: ubuntu-latest env: PLUGINS: restify steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test + - uses: ./.github/actions/testagent/start + - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/install + - run: yarn test:plugins:ci + - uses: ./.github/actions/node/oldest-maintenance-lts + - run: yarn test:plugins:ci rhea: runs-on: ubuntu-latest @@ -866,7 +888,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:plugins:ci - if: always() @@ -892,7 +914,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:plugins:ci - run: yarn test:plugins:upstream diff --git a/.github/workflows/appsec.yml b/.github/workflows/appsec.yml index 1178217a473..7f3f46def9e 100644 --- a/.github/workflows/appsec.yml +++ b/.github/workflows/appsec.yml @@ -19,7 +19,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:appsec:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -43,7 +43,7 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install with: cache: 'true' @@ -70,7 +70,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -127,7 +127,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/active-lts # TODO: change this to latest once we figure out the `fresh` bug - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -140,7 +140,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -153,7 +153,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -172,7 +172,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -191,7 +191,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -264,7 +264,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -290,7 +290,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -303,7 +303,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -316,7 +316,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -348,6 +348,6 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 083b2fe42b7..a2ac949b7f0 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -14,5 +14,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn audit diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 904c52e52a4..8725bf8b396 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 with: languages: ${{ matrix.language }} config-file: .github/codeql_config.yml @@ -48,7 +48,7 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Autobuild - uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 diff --git a/.github/workflows/debugger.yml b/.github/workflows/debugger.yml index 0dab72f0638..04caa35f700 100644 --- a/.github/workflows/debugger.yml +++ b/.github/workflows/debugger.yml @@ -29,8 +29,6 @@ jobs: - uses: ./.github/actions/install - run: yarn test:debugger:ci - run: yarn test:integration:debugger - env: - OPTIONS_OVERRIDE: 1 - if: always() uses: ./.github/actions/testagent/logs with: diff --git a/.github/workflows/dependabot-automation.yml b/.github/workflows/dependabot-automation.yml index 6a7bdfc0983..4ee0a37f0cd 100644 --- a/.github/workflows/dependabot-automation.yml +++ b/.github/workflows/dependabot-automation.yml @@ -1,6 +1,9 @@ name: 'Dependabot Automation' -on: pull_request_target +on: + pull_request_target: + types: + - opened jobs: dependabot: diff --git a/.github/workflows/eslint-rules.yml b/.github/workflows/eslint-rules.yml index 2909c255cfd..2c616d18d8f 100644 --- a/.github/workflows/eslint-rules.yml +++ b/.github/workflows/eslint-rules.yml @@ -18,6 +18,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:eslint-rules diff --git a/.github/workflows/flakiness.yml b/.github/workflows/flakiness.yml index 5ad8a75f18f..845789af22e 100644 --- a/.github/workflows/flakiness.yml +++ b/.github/workflows/flakiness.yml @@ -1,6 +1,7 @@ -name: Flakiness Report +name: '[Flakiness Report]' on: + workflow_dispatch: schedule: - cron: '0 6 * * 1' @@ -25,3 +26,18 @@ jobs: version: '' - run: npm install octokit - run: node scripts/flakiness.mjs + - run: cat flakiness.md >> $GITHUB_STEP_SUMMARY + - id: slack + run: echo "report=$(cat flakiness.txt)" >> $GITHUB_OUTPUT + - uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.0 + if: github.event_name == 'schedule' + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + blocks: + - type: "section" + text: + type: "mrkdwn" + text: "${{ steps.slack.outputs.report }}" diff --git a/.github/workflows/llmobs.yml b/.github/workflows/llmobs.yml index e10eef3b348..b0dc865358e 100644 --- a/.github/workflows/llmobs.yml +++ b/.github/workflows/llmobs.yml @@ -46,7 +46,7 @@ jobs: - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash @@ -68,7 +68,7 @@ jobs: - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash @@ -89,7 +89,7 @@ jobs: - uses: ./.github/actions/install - run: yarn test:llmobs:plugins:ci shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:llmobs:plugins:ci shell: bash - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -110,7 +110,29 @@ jobs: - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest + - run: yarn test:plugins:ci + - run: yarn test:llmobs:plugins:ci + shell: bash + - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + - if: always() + uses: ./.github/actions/testagent/logs + with: + suffix: llmobs-${{ github.job }} + + ai: + runs-on: ubuntu-latest + env: + PLUGINS: ai + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: ./.github/actions/testagent/start + - uses: ./.github/actions/node/oldest-maintenance-lts + - uses: ./.github/actions/install + - run: yarn test:plugins:ci + - run: yarn test:llmobs:plugins:ci + shell: bash + - uses: ./.github/actions/node/latest - run: yarn test:plugins:ci - run: yarn test:llmobs:plugins:ci shell: bash diff --git a/.github/workflows/platform.yml b/.github/workflows/platform.yml index 8776fac7dc7..b4cb8e0d0e7 100644 --- a/.github/workflows/platform.yml +++ b/.github/workflows/platform.yml @@ -24,7 +24,7 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:core:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:core:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -298,7 +298,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:trace:guardrails:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 @@ -344,6 +344,6 @@ jobs: - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:shimmer:ci - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - run: yarn test:shimmer:ci - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 diff --git a/.github/workflows/profiling.yml b/.github/workflows/profiling.yml index 80a21b93ea8..4d9bb3501bb 100644 --- a/.github/workflows/profiling.yml +++ b/.github/workflows/profiling.yml @@ -19,7 +19,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:profiler:ci - run: yarn test:integration:profiler @@ -48,7 +48,7 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install with: cache: 'true' diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml index 2369d884ee2..148d24bbe50 100644 --- a/.github/workflows/project.yml +++ b/.github/workflows/project.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest # NOTE: Ok this next bit seems unnecessary, right? The problem is that # this repo is currently incompatible with npm, at least with the # devDependencies. While this is intended to be corrected, it hasn't yet, @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn lint @@ -57,7 +57,7 @@ jobs: pull-requests: write steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - name: Compute module size tree and report uses: qard/heaviest-objects-in-the-universe@e2af4ff3a88e5fe507bd2de1943b015ba2ddda66 # v1.0.0 @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn type:test - run: yarn type:doc @@ -94,7 +94,7 @@ jobs: # runs-on: ubuntu-latest # steps: # - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - # - uses: ./.github/actions/node/active-lts + # - uses: ./.github/actions/node/latest # - uses: ./.github/actions/install # - run: node scripts/verify-ci-config.js @@ -111,7 +111,7 @@ jobs: fetch-depth: 0 - name: Setup Node.js - uses: ./.github/actions/node/active-lts + uses: ./.github/actions/node/latest - name: Install dependencies uses: ./.github/actions/install diff --git a/.github/workflows/serverless.yml b/.github/workflows/serverless.yml index 3e6bbfebefe..5190c981fc9 100644 --- a/.github/workflows/serverless.yml +++ b/.github/workflows/serverless.yml @@ -38,11 +38,7 @@ jobs: aws-sdk: strategy: matrix: - include: - - node-version: latest - node-opts: --no-async-context-frame - - node-version: oldest - node-opts: '' + node-version: [oldest, latest] runs-on: ubuntu-latest services: localstack: @@ -86,8 +82,6 @@ jobs: version: ${{ matrix.node-version }} - uses: ./.github/actions/install - run: yarn test:plugins:ci - env: - NODE_OPTIONS: ${{ matrix.node-opts }} - if: always() uses: ./.github/actions/testagent/logs with: @@ -97,6 +91,12 @@ jobs: azure-functions: runs-on: ubuntu-latest services: + azurite: + image: mcr.microsoft.com/azure-storage/azurite:3.34.0 + ports: + - "127.0.0.1:10000:10000" + - "127.0.0.1:10001:10001" + - "127.0.0.1:10002:10002" azureservicebusemulator: image: mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2 ports: @@ -118,7 +118,11 @@ jobs: SERVICES: azureservicebusemulator,azuresqledge steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./.github/actions/plugins/test + - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/install + - run: npm install -g azure-functions-core-tools@4.1.0 + - run: echo "$(dirname $(which func))" >> $GITHUB_PATH + - run: yarn test:plugins:ci azure-service-bus: runs-on: ubuntu-latest diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 92ba0e32c9a..f1cc093e83e 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -31,10 +31,11 @@ jobs: main: needs: - build-artifacts - uses: DataDog/system-tests/.github/workflows/system-tests.yml@main + uses: DataDog/system-tests/.github/workflows/system-tests.yml@b2523d82a7fcffb5ca642ee7b76eb476fbef04fe secrets: inherit permissions: contents: read + id-token: write packages: write with: library: nodejs diff --git a/.github/workflows/test-optimization.yml b/.github/workflows/test-optimization.yml index c6cc276f334..744954db97e 100644 --- a/.github/workflows/test-optimization.yml +++ b/.github/workflows/test-optimization.yml @@ -178,7 +178,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:plugins:ci - if: always() @@ -195,7 +195,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/testagent/start - - uses: ./.github/actions/node/active-lts + - uses: ./.github/actions/node/latest - uses: ./.github/actions/install - run: yarn test:plugins:ci - if: always() diff --git a/.gitlab/one-pipeline.locked.yml b/.gitlab/one-pipeline.locked.yml index 514056de50a..b8554f3ae89 100644 --- a/.gitlab/one-pipeline.locked.yml +++ b/.gitlab/one-pipeline.locked.yml @@ -1,4 +1,4 @@ # DO NOT EDIT THIS FILE MANUALLY # This file is auto-generated by automation. include: - - remote: https://gitlab-templates.ddbuild.io/libdatadog/one-pipeline/ca/a0486057161f85a77e39ad2aa60ac66bb52414696d9b3dd87177df1057b11295/one-pipeline.yml + - remote: https://gitlab-templates.ddbuild.io/libdatadog/one-pipeline/ca/50d49f6898ce86e93326856210e8ab6526895273cb6341ac2d7d0e6c1c14e31e/one-pipeline.yml diff --git a/CODEOWNERS b/CODEOWNERS index a6519aad2ca..9329ad7befb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -61,9 +61,11 @@ /packages/datadog-plugin-openai/ @DataDog/ml-observability /packages/datadog-plugin-langchain/ @DataDog/ml-observability /packages/datadog-plugin-google-cloud-vertexai/ @DataDog/ml-observability +/packages/datadog-plugin-ai/ @DataDog/ml-observability /packages/datadog-instrumentations/src/openai.js @DataDog/ml-observability /packages/datadog-instrumentations/src/langchain.js @DataDog/ml-observability /packages/datadog-instrumentations/src/google-cloud-vertexai.js @DataDog/ml-observability +/packages/datadog-instrumentations/src/ai.js @DataDog/ml-observability /packages/datadog-plugin-aws-sdk/src/services/bedrockruntime @DataDog/ml-observability /packages/datadog-plugin-aws-sdk/test/bedrockruntime.spec.js @DataDog/ml-observability diff --git a/README.md b/README.md index 01ef83e4d11..939c0814cfe 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,6 @@ Most of the documentation for `dd-trace` is available on these webpages: ## Version Release Lines and Maintenance -> **Node.js v24 Notice**: We're currently adding compatibility for Node.js v24. To use the tracer with your application either continue to use Node.js v22 (LTS), or do both of the following as a workaround: -> * Install v5.52.0 (or newer) of the tracer -> * Set `--no-async-context-frame` either using a CLI argument or via `NODE_OPTIONS` -> Once support for Node.js v24 is complete this flag will no longer be needed. - | Release Line | Latest Version | Node.js | [SSI](https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/single-step-apm/?tab=linuxhostorvm) | [K8s Injection](https://docs.datadoghq.com/tracing/trace_collection/library_injection_local/?tab=kubernetes) |Status |Initial Release | End of Life | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | | [`v1`](https://github.com/DataDog/dd-trace-js/tree/v1.x) | ![npm v1](https://img.shields.io/npm/v/dd-trace/legacy-v1?color=white&label=%20&style=flat-square) | `>= v12` | NO | NO | **EOL** | 2021-07-13 | 2022-02-25 | diff --git a/docker-compose.yml b/docker-compose.yml index e6aad725cc7..37235701f52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,12 @@ services: image: aerospike:ce-6.4.0.3 ports: - "127.0.0.1:3000-3002:3000-3002" + azurite: + image: mcr.microsoft.com/azure-storage/azurite:3.34.0 + ports: + - "127.0.0.1:10000:10000" + - "127.0.0.1:10001:10001" + - "127.0.0.1:10002:10002" azureservicebusemulator: image: mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2 ports: @@ -171,7 +177,7 @@ services: - LDAP_PASSWORDS=password1,password2 testagent: - image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.27.4 + image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.31.1 ports: - "127.0.0.1:9126:9126" environment: diff --git a/eslint.config.mjs b/eslint.config.mjs index ae9862d6a0f..936b620ce35 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -543,7 +543,6 @@ export default [ sinon: 'readonly', expect: 'readonly', proxyquire: 'readonly', - withVersions: 'readonly', } }, plugins: { diff --git a/integration-tests/helpers/index.js b/integration-tests/helpers/index.js index 2aa8d29c7fd..a41de56ee92 100644 --- a/integration-tests/helpers/index.js +++ b/integration-tests/helpers/index.js @@ -113,7 +113,10 @@ function assertTelemetryPoints (pid, msgs, expectedTelemetryPoints) { runtime_name: 'nodejs', runtime_version: process.versions.node, tracer_version: require('../../package.json').version, - pid: Number(pid) + pid: Number(pid), + result: 'unknown', + result_reason: 'unknown', + result_class: 'unknown' } } } diff --git a/integration-tests/playwright/playwright.spec.js b/integration-tests/playwright/playwright.spec.js index eccc4e908b1..94126913fb7 100644 --- a/integration-tests/playwright/playwright.spec.js +++ b/integration-tests/playwright/playwright.spec.js @@ -77,8 +77,8 @@ versions.forEach((version) => { let sandbox, cwd, receiver, childProcess, webAppPort, webPortWithRedirect before(async function () { - // bump from 60 to 90 seconds because playwright is heavy - this.timeout(90000) + // Usually takes under 30 seconds but sometimes the server is really slow. + this.timeout(300_000) sandbox = await createSandbox([`@playwright/test@${version}`, 'typescript'], true) cwd = sandbox.folder const { NODE_OPTIONS, ...restOfEnv } = process.env diff --git a/integration-tests/profiler/profiler.spec.js b/integration-tests/profiler/profiler.spec.js index b413eac5bc7..81a0040795d 100644 --- a/integration-tests/profiler/profiler.spec.js +++ b/integration-tests/profiler/profiler.spec.js @@ -389,8 +389,8 @@ describe('profiler', () => { for (const label of sample.label) { switch (label.key) { case tsKey: ts = label.num; break - case spanKey: spanId = label.str; break - case rootSpanKey: rootSpanId = label.str; break + case spanKey: spanId = label.num; break + case rootSpanKey: rootSpanId = label.num; break case endpointKey: endpoint = label.str; break case threadNameKey: threadName = label.str; break case threadIdKey: threadId = label.str; break diff --git a/package.json b/package.json index 6eb14c8833e..b51ff3e9545 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dd-trace", - "version": "5.62.0", + "version": "5.63.0", "description": "Datadog APM tracing client for JavaScript", "main": "index.js", "typings": "index.d.ts", @@ -114,7 +114,7 @@ ], "dependencies": { "@datadog/libdatadog": "0.7.0", - "@datadog/native-appsec": "10.0.1", + "@datadog/native-appsec": "10.1.0", "@datadog/native-iast-taint-tracking": "4.0.0", "@datadog/native-metrics": "3.1.1", "@datadog/pprof": "5.9.0", diff --git a/packages/datadog-instrumentations/src/ai.js b/packages/datadog-instrumentations/src/ai.js new file mode 100644 index 00000000000..3aec3d5fcdb --- /dev/null +++ b/packages/datadog-instrumentations/src/ai.js @@ -0,0 +1,140 @@ +'use strict' + +const { addHook } = require('./helpers/instrument') +const shimmer = require('../../datadog-shimmer') + +const { channel, tracingChannel } = require('dc-polyfill') +const toolCreationChannel = channel('dd-trace:vercel-ai:tool') + +const TRACED_FUNCTIONS = { + generateText: wrapWithTracer, + streamText: wrapWithTracer, + generateObject: wrapWithTracer, + streamObject: wrapWithTracer, + embed: wrapWithTracer, + embedMany: wrapWithTracer, + tool: wrapTool +} + +const vercelAiTracingChannel = tracingChannel('dd-trace:vercel-ai') +const vercelAiSpanSetAttributesChannel = channel('dd-trace:vercel-ai:span:setAttributes') + +const noopTracer = { + startActiveSpan () { + const fn = arguments[arguments.length - 1] + + const span = { + spanContext () { return { traceId: '', spanId: '', traceFlags: 0 } }, + setAttribute () { return this }, + setAttributes () { return this }, + addEvent () { return this }, + addLink () { return this }, + addLinks () { return this }, + setStatus () { return this }, + updateName () { return this }, + end () { return this }, + isRecording () { return false }, + recordException () { return this } + } + + return fn(span) + } +} + +function wrapTracer (tracer) { + if (Object.hasOwn(tracer, Symbol.for('_dd.wrapped'))) return + + shimmer.wrap(tracer, 'startActiveSpan', function (startActiveSpan) { + return function () { + const name = arguments[0] + const options = arguments.length > 2 ? (arguments[1] ?? {}) : {} // startActiveSpan(name, fn) + const cb = arguments[arguments.length - 1] + + const ctx = { + name, + attributes: options.attributes ?? {} + } + + arguments[arguments.length - 1] = shimmer.wrapFunction(cb, function (originalCb) { + return function (span) { + shimmer.wrap(span, 'end', function (spanEnd) { + return function () { + vercelAiTracingChannel.asyncEnd.publish(ctx) + return spanEnd.apply(this, arguments) + } + }) + + shimmer.wrap(span, 'setAttributes', function (setAttributes) { + return function (attributes) { + vercelAiSpanSetAttributesChannel.publish({ ctx, attributes }) + return setAttributes.apply(this, arguments) + } + }) + + shimmer.wrap(span, 'recordException', function (recordException) { + return function (exception) { + ctx.error = exception + vercelAiTracingChannel.error.publish(ctx) + return recordException.apply(this, arguments) + } + }) + + return originalCb.apply(this, arguments) + } + }) + + return vercelAiTracingChannel.start.runStores(ctx, () => { + const result = startActiveSpan.apply(this, arguments) + vercelAiTracingChannel.end.publish(ctx) + return result + }) + } + }) + + Object.defineProperty(tracer, Symbol.for('_dd.wrapped'), { value: true }) +} + +function wrapWithTracer (fn) { + return function () { + const options = arguments[0] + + options.experimental_telemetry ??= { isEnabled: true, tracer: noopTracer } + wrapTracer(options.experimental_telemetry.tracer) + + return fn.apply(this, arguments) + } +} + +function wrapTool (tool) { + return function () { + const args = arguments[0] + toolCreationChannel.publish(args) + + return tool.apply(this, arguments) + } +} + +// CJS exports +addHook({ + name: 'ai', + versions: ['>=4.0.0'], +}, exports => { + for (const [fnName, patchingFn] of Object.entries(TRACED_FUNCTIONS)) { + exports = shimmer.wrap(exports, fnName, patchingFn, { replaceGetter: true }) + } + + return exports +}) + +// ESM exports +addHook({ + name: 'ai', + versions: ['>=4.0.0'], + file: 'dist/index.mjs' +}, exports => { + for (const [fnName, patchingFn] of Object.entries(TRACED_FUNCTIONS)) { + exports = shimmer.wrap(exports, fnName, patchingFn, { replaceGetter: true }) + } + + return exports +}) diff --git a/packages/datadog-instrumentations/src/couchbase.js b/packages/datadog-instrumentations/src/couchbase.js index f4024883146..7a648b7ef71 100644 --- a/packages/datadog-instrumentations/src/couchbase.js +++ b/packages/datadog-instrumentations/src/couchbase.js @@ -3,8 +3,7 @@ const { errorMonitor } = require('events') const { channel, - addHook, - AsyncResource + addHook } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') @@ -24,31 +23,50 @@ function wrapAllNames (names, action) { names.forEach(name => action(name)) } -// semver >=2 <3 -function wrapMaybeInvoke (_maybeInvoke) { - const wrapped = function (fn, args) { - if (!Array.isArray(args)) return _maybeInvoke.apply(this, arguments) - - const callbackIndex = args.length - 1 - const callback = args[callbackIndex] +function wrapCallback (callback, ctx, channelPrefix) { + const callbackStartCh = channel(`${channelPrefix}:callback:start`) + const callbackFinishCh = channel(`${channelPrefix}:callback:finish`) - if (typeof callback === 'function') { - args[callbackIndex] = AsyncResource.bind(callback) + const wrapped = callbackStartCh.runStores(ctx, () => { + return function (...args) { + return callbackFinishCh.runStores(ctx, () => { + return callback.apply(this, args) + }) } - - return _maybeInvoke.apply(this, arguments) - } + }) + Object.defineProperty(wrapped, '_dd_wrapped', { value: true }) return wrapped } function wrapQuery (query) { - const wrapped = function (q, params, callback) { - if (typeof arguments[arguments.length - 1] === 'function') { - arguments[arguments.length - 1] = AsyncResource.bind(arguments[arguments.length - 1]) + return function (q, params, callback) { + const cb = arguments[arguments.length - 1] + if (typeof cb === 'function') { + const ctx = {} + arguments[arguments.length - 1] = wrapCallback(cb, ctx, 'apm:couchbase:query') } return query.apply(this, arguments) } +} + +function wrapCallbackFinish (callback, thisArg, _args, errorCh, finishCh, ctx, channelPrefix) { + const callbackStartCh = channel(`${channelPrefix}:callback:start`) + const callbackFinishCh = channel(`${channelPrefix}:callback:finish`) + + const wrapped = callbackStartCh.runStores(ctx, () => { + return function finish (error, result) { + return callbackFinishCh.runStores(ctx, () => { + if (error) { + ctx.error = error + errorCh.publish(ctx) + } + finishCh.publish(ctx) + return callback.apply(thisArg, [error, result]) + }) + } + }) + Object.defineProperty(wrapped, '_dd_wrapped', { value: true }) return wrapped } @@ -62,31 +80,24 @@ function wrap (prefix, fn) { return fn.apply(this, arguments) } - const callbackIndex = findCallbackIndex(arguments) + const callbackIndex = findCallbackIndex(arguments, 1) if (callbackIndex < 0) return fn.apply(this, arguments) - const callbackResource = new AsyncResource('bound-anonymous-fn') - const asyncResource = new AsyncResource('bound-anonymous-fn') - - return asyncResource.runInAsyncScope(() => { - const cb = callbackResource.bind(arguments[callbackIndex]) + const ctx = { bucket: { name: this.name || this._name }, seedNodes: this._dd_hosts } + return startCh.runStores(ctx, () => { + const cb = arguments[callbackIndex] - startCh.publish({ bucket: { name: this.name || this._name }, seedNodes: this._dd_hosts }) - - arguments[callbackIndex] = shimmer.wrapFunction(cb, cb => asyncResource.bind(function (error, result) { - if (error) { - errorCh.publish(error) - } - finishCh.publish(result) - return cb.apply(this, arguments) - })) + arguments[callbackIndex] = shimmer.wrapFunction(cb, (cb) => { + return wrapCallbackFinish(cb, this, arguments, errorCh, finishCh, ctx, prefix) + }) try { return fn.apply(this, arguments) } catch (error) { + ctx.error = error error.stack // trigger getting the stack at the original throwing point - errorCh.publish(error) + errorCh.publish(ctx) throw error } @@ -95,6 +106,26 @@ function wrap (prefix, fn) { return wrapped } +// semver >=2 <3 +function wrapMaybeInvoke (_maybeInvoke, channelPrefix) { + return function (fn, args) { + if (!Array.isArray(args)) return _maybeInvoke.apply(this, arguments) + + const callbackIndex = findCallbackIndex(args, 0) + + if (callbackIndex === -1) return _maybeInvoke.apply(this, arguments) + + const callback = args[callbackIndex] + + if (typeof callback === 'function' && !callback._dd_wrapped) { + const ctx = {} + args[callbackIndex] = wrapCallback(callback, ctx, channelPrefix) + } + + return _maybeInvoke.apply(this, arguments) + } +} + // semver >=3 function wrapCBandPromise (fn, name, startData, thisArg, args) { @@ -104,36 +135,36 @@ function wrapCBandPromise (fn, name, startData, thisArg, args) { if (!startCh.hasSubscribers) return fn.apply(thisArg, args) - const asyncResource = new AsyncResource('bound-anonymous-fn') - const callbackResource = new AsyncResource('bound-anonymous-fn') - - return asyncResource.runInAsyncScope(() => { - startCh.publish(startData) - + const ctx = startData + return startCh.runStores(ctx, () => { try { const cbIndex = findCallbackIndex(args, 1) if (cbIndex >= 0) { // v3 offers callback or promises event handling // NOTE: this does not work with v3.2.0-3.2.1 cluster.query, as there is a bug in the couchbase source code - const cb = callbackResource.bind(args[cbIndex]) - args[cbIndex] = shimmer.wrapFunction(cb, cb => asyncResource.bind(function (error, result) { - if (error) { - errorCh.publish(error) - } - finishCh.publish({ result }) - return cb.apply(thisArg, arguments) - })) + args[cbIndex] = shimmer.wrapFunction(args[cbIndex], (cb) => { + return wrapCallbackFinish(cb, thisArg, args, errorCh, finishCh, ctx, `apm:couchbase:${name}`) + }) } const res = fn.apply(thisArg, args) // semver >=3 will always return promise by default res.then( - asyncResource.bind((result) => finishCh.publish({ result })), - asyncResource.bind((err) => errorCh.publish(err))) + (result) => { + ctx.result = result + finishCh.publish(ctx) + }, + (err) => { + ctx.error = err + errorCh.publish(ctx) + finishCh.publish(ctx) + } + ) return res } catch (e) { e.stack - errorCh.publish(e) + ctx.error = e + errorCh.publish(ctx) throw e } }) @@ -160,11 +191,14 @@ function wrapV3Query (query) { // semver >=2 <3 addHook({ name: 'couchbase', file: 'lib/bucket.js', versions: ['^2.6.12'] }, Bucket => { + shimmer.wrap(Bucket.prototype, '_maybeInvoke', maybeInvoke => { + return wrapMaybeInvoke(maybeInvoke, 'apm:couchbase:bucket:maybeInvoke') + }) + const startCh = channel('apm:couchbase:query:start') const finishCh = channel('apm:couchbase:query:finish') const errorCh = channel('apm:couchbase:query:error') - shimmer.wrap(Bucket.prototype, '_maybeInvoke', maybeInvoke => wrapMaybeInvoke(maybeInvoke)) shimmer.wrap(Bucket.prototype, 'query', query => wrapQuery(query)) shimmer.wrap(Bucket.prototype, '_n1qlReq', _n1qlReq => function (host, q, adhoc, emitter) { @@ -176,24 +210,25 @@ addHook({ name: 'couchbase', file: 'lib/bucket.js', versions: ['^2.6.12'] }, Buc const n1qlQuery = getQueryResource(q) - const asyncResource = new AsyncResource('bound-anonymous-fn') - return asyncResource.runInAsyncScope(() => { - startCh.publish({ resource: n1qlQuery, bucket: { name: this.name || this._name }, seedNodes: this._dd_hosts }) - - emitter.once('rows', asyncResource.bind(() => { - finishCh.publish() - })) + const ctx = { resource: n1qlQuery, bucket: { name: this.name || this._name }, seedNodes: this._dd_hosts } + return startCh.runStores(ctx, () => { + emitter.once('rows', () => { + finishCh.publish(ctx) + }) - emitter.once(errorMonitor, asyncResource.bind((error) => { - errorCh.publish(error) - finishCh.publish() - })) + emitter.once(errorMonitor, (error) => { + if (!error) return + ctx.error = error + errorCh.publish(ctx) + finishCh.publish(ctx) + }) try { return _n1qlReq.apply(this, arguments) } catch (err) { err.stack // trigger getting the stack at the original throwing point - errorCh.publish(err) + ctx.error = err + errorCh.publish(ctx) throw err } @@ -208,9 +243,11 @@ addHook({ name: 'couchbase', file: 'lib/bucket.js', versions: ['^2.6.12'] }, Buc }) addHook({ name: 'couchbase', file: 'lib/cluster.js', versions: ['^2.6.12'] }, Cluster => { - shimmer.wrap(Cluster.prototype, '_maybeInvoke', maybeInvoke => wrapMaybeInvoke(maybeInvoke)) - shimmer.wrap(Cluster.prototype, 'query', query => wrapQuery(query)) + shimmer.wrap(Cluster.prototype, '_maybeInvoke', maybeInvoke => { + return wrapMaybeInvoke(maybeInvoke, 'apm:couchbase:cluster:maybeInvoke') + }) + shimmer.wrap(Cluster.prototype, 'query', query => wrapQuery(query)) shimmer.wrap(Cluster.prototype, 'openBucket', openBucket => { return function () { const bucket = openBucket.apply(this, arguments) diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js index eadf031a13b..80561a3e240 100644 --- a/packages/datadog-instrumentations/src/helpers/hooks.js +++ b/packages/datadog-instrumentations/src/helpers/hooks.js @@ -30,6 +30,7 @@ module.exports = { '@smithy/smithy-client': () => require('../aws-sdk'), '@vitest/runner': { esmFirst: true, fn: () => require('../vitest') }, aerospike: () => require('../aerospike'), + ai: () => require('../ai'), amqp10: () => require('../amqp10'), amqplib: () => require('../amqplib'), avsc: () => require('../avsc'), diff --git a/packages/datadog-instrumentations/src/helpers/register.js b/packages/datadog-instrumentations/src/helpers/register.js index 23fa29cfaa3..5fbefd38649 100644 --- a/packages/datadog-instrumentations/src/helpers/register.js +++ b/packages/datadog-instrumentations/src/helpers/register.js @@ -9,7 +9,6 @@ const log = require('../../../dd-trace/src/log') const checkRequireCache = require('./check-require-cache') const telemetry = require('../../../dd-trace/src/guardrails/telemetry') const { isInServerlessEnvironment } = require('../../../dd-trace/src/serverless') -const { isFalse, isTrue, normalizePluginEnvName } = require('../../../dd-trace/src/util') const { getEnvironmentVariables } = require('../../../dd-trace/src/config-helper') const envs = getEnvironmentVariables() @@ -25,22 +24,8 @@ const names = Object.keys(hooks) const pathSepExpr = new RegExp(`\\${path.sep}`, 'g') const disabledInstrumentations = new Set( - DD_TRACE_DISABLED_INSTRUMENTATIONS?.split(',').map(name => normalizePluginEnvName(name, true)) ?? [] + DD_TRACE_DISABLED_INSTRUMENTATIONS?.split(',') ) -const reenabledInstrumentations = new Set() - -// Check for DD_TRACE__ENABLED environment variables -for (const [key, value] of Object.entries(envs)) { - const match = key.match(/^DD_TRACE_(.+)_ENABLED$/) - if (match && value) { - const integration = normalizePluginEnvName(match[1], true) - if (isFalse(value)) { - disabledInstrumentations.add(integration) - } else if (isTrue(value)) { - reenabledInstrumentations.add(integration) - } - } -} const loadChannel = channel('dd-trace:instrumentation:load') @@ -65,8 +50,7 @@ const allInstrumentations = {} // TODO: make this more efficient for (const packageName of names) { - const normalizedPackageName = normalizePluginEnvName(packageName, true) - if (disabledInstrumentations.has(normalizedPackageName)) continue + if (disabledInstrumentations.has(packageName)) continue const hookOptions = {} @@ -75,10 +59,6 @@ for (const packageName of names) { if (hook !== null && typeof hook === 'object') { if (hook.serverless === false && isInServerlessEnvironment()) continue - // some integrations are disabled by default, but can be enabled by setting - // the DD_TRACE__ENABLED environment variable to true - if (hook.disabled && !reenabledInstrumentations.has(normalizedPackageName)) continue - hookOptions.internals = hook.esmFirst hook = hook.fn } diff --git a/packages/datadog-instrumentations/src/hono.js b/packages/datadog-instrumentations/src/hono.js index 894851a060f..7f1a8d40f04 100644 --- a/packages/datadog-instrumentations/src/hono.js +++ b/packages/datadog-instrumentations/src/hono.js @@ -17,8 +17,14 @@ function wrapFetch (fetch) { } } +function onErrorFn (error, _context_) { + throw error +} + function wrapCompose (compose) { - return function (middleware, onError, onNotFound) { + return function (middlewares, onError, onNotFound) { + onError ??= onErrorFn + const instrumentedOnError = (...args) => { const [error, context] = args const req = context.env.incoming @@ -26,23 +32,20 @@ function wrapCompose (compose) { return onError(...args) } - const instrumentedMiddlewares = middleware.map(h => { + const instrumentedMiddlewares = middlewares.map(h => { const [[fn, meta], params] = h - // TODO: handle middleware instrumentation const instrumentedFn = (...args) => { const context = args[0] - const req = context.env.incoming - const route = meta.path routeChannel.publish({ - req, - route + req: context.env.incoming, + route: meta?.path }) return fn(...args) } return [[instrumentedFn, meta], params] }) - return compose.apply(this, [instrumentedMiddlewares, instrumentedOnError, onNotFound]) + return compose.call(this, instrumentedMiddlewares, instrumentedOnError, onNotFound) } } diff --git a/packages/datadog-instrumentations/src/knex.js b/packages/datadog-instrumentations/src/knex.js index ac2848656bc..7f6c2f3c896 100644 --- a/packages/datadog-instrumentations/src/knex.js +++ b/packages/datadog-instrumentations/src/knex.js @@ -1,10 +1,11 @@ 'use strict' -const { addHook, channel, AsyncResource } = require('./helpers/instrument') +const { addHook, channel } = require('./helpers/instrument') const { wrapThen } = require('./helpers/promise') const shimmer = require('../../datadog-shimmer') const startRawQueryCh = channel('datadog:knex:raw:start') +const rawQuerySubscribes = channel('datadog:knex:raw:subscribes') const finishRawQueryCh = channel('datadog:knex:raw:finish') patch('lib/query/builder.js') @@ -22,8 +23,8 @@ function patch (file) { }) } -function finish () { - finishRawQueryCh.publish() +function finish (context, cb) { + finishRawQueryCh.runStores(context, cb) } addHook({ @@ -43,21 +44,18 @@ addHook({ return raw.apply(this, arguments) } - const asyncResource = new AsyncResource('bound-anonymous-fn') - - return asyncResource.runInAsyncScope(() => { - startRawQueryCh.publish({ sql, dialect: this.dialect }) - + const context = { sql, dialect: this.dialect } + return startRawQueryCh.runStores(context, () => { const rawResult = raw.apply(this, arguments) shimmer.wrap(rawResult, 'then', originalThen => function () { - return asyncResource.runInAsyncScope(() => { - arguments[0] = wrapCallbackWithFinish(arguments[0], finish) - if (arguments[1]) arguments[1] = wrapCallbackWithFinish(arguments[1], finish) + return rawQuerySubscribes.runStores(context, () => { + arguments[0] = wrapCallbackWithFinish(arguments[0], finish, context) + if (arguments[1]) arguments[1] = wrapCallbackWithFinish(arguments[1], finish, context) const originalThenResult = originalThen.apply(this, arguments) shimmer.wrap(originalThenResult, 'catch', originalCatch => function () { - arguments[0] = wrapCallbackWithFinish(arguments[0], finish) + arguments[0] = wrapCallbackWithFinish(arguments[0], finish, context) return originalCatch.apply(this, arguments) }) @@ -66,8 +64,8 @@ addHook({ }) shimmer.wrap(rawResult, 'asCallback', originalAsCallback => function () { - return asyncResource.runInAsyncScope(() => { - arguments[0] = wrapCallbackWithFinish(arguments[0], finish) + return rawQuerySubscribes.runStores(context, () => { + arguments[0] = wrapCallbackWithFinish(arguments[0], finish, context) return originalAsCallback.apply(this, arguments) }) }) @@ -75,14 +73,14 @@ addHook({ return rawResult }) }) + return Knex }) -function wrapCallbackWithFinish (callback, finish) { +function wrapCallbackWithFinish (callback, finish, context) { if (typeof callback !== 'function') return callback return shimmer.wrapFunction(callback, callback => function () { - finish() - callback.apply(this, arguments) + finish(context, () => callback.apply(this, arguments)) }) } diff --git a/packages/datadog-instrumentations/src/mongodb-core.js b/packages/datadog-instrumentations/src/mongodb-core.js index 4a27e919a3a..b6f187ae542 100644 --- a/packages/datadog-instrumentations/src/mongodb-core.js +++ b/packages/datadog-instrumentations/src/mongodb-core.js @@ -199,17 +199,15 @@ function instrumentPromise (operation, command, instance, args, server, ns, ops, return startCh.runStores(ctx, () => { const promise = command.apply(instance, args) - return promise.then(function (res) { + promise.then(function (res) { ctx.result = res - return finishCh.runStores(ctx, () => { - return res - }) + finishCh.publish(ctx) }, function (err) { ctx.error = err errorCh.publish(ctx) finishCh.publish(ctx) - - throw err }) + + return promise }) } diff --git a/packages/datadog-instrumentations/src/next.js b/packages/datadog-instrumentations/src/next.js index 7bbdef0a4a7..fe35fc18420 100644 --- a/packages/datadog-instrumentations/src/next.js +++ b/packages/datadog-instrumentations/src/next.js @@ -162,14 +162,16 @@ function instrument (req, res, handler, error) { // promise should only reject when propagateError is true: // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L547 - return promise.then( + promise.then( result => finish(ctx, result), err => finish(ctx, null, err) ) + return promise } catch (e) { // this will probably never happen as the handler caller is an async function: // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L420 - return finish(ctx, null, e) + finish(ctx, null, e) + throw e } }) } @@ -198,12 +200,6 @@ function finish (ctx, result, err) { } finishChannel.publish(ctx) - - if (err) { - throw err - } - - return result } // also wrapped in dist/server/future/route-handlers/app-route-route-handler.js diff --git a/packages/datadog-instrumentations/src/pg.js b/packages/datadog-instrumentations/src/pg.js index c2cc51f1512..3bfacbbaf54 100644 --- a/packages/datadog-instrumentations/src/pg.js +++ b/packages/datadog-instrumentations/src/pg.js @@ -2,8 +2,7 @@ const { channel, - addHook, - AsyncResource + addHook } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') @@ -33,8 +32,6 @@ function wrapQuery (query) { return query.apply(this, arguments) } - const callbackResource = new AsyncResource('bound-anonymous-fn') - const asyncResource = new AsyncResource('bound-anonymous-fn') const processId = this.processID const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' @@ -46,8 +43,9 @@ function wrapQuery (query) { const stream = typeof textPropObj.read === 'function' // Only alter `text` property if safe to do so. Initially, it's a property, not a getter. + let originalText if (!textProp || textProp.configurable) { - const originalText = textPropObj.text + originalText = textPropObj.text Object.defineProperty(textPropObj, 'text', { get () { @@ -55,25 +53,24 @@ function wrapQuery (query) { } }) } - - return asyncResource.runInAsyncScope(() => { - const abortController = new AbortController() - - startCh.publish({ - params: this.connectionParameters, - query: textPropObj, - processId, - abortController, - stream - }) - - const finish = asyncResource.bind(function (error, res) { - if (error) { - errorCh.publish(error) - } - finishCh.publish({ result: res?.rows }) - }) - + const abortController = new AbortController() + const ctx = { + params: this.connectionParameters, + query: textPropObj, + originalText, + processId, + abortController, + stream + } + const finish = (error, res) => { + if (error) { + ctx.error = error + errorCh.publish(ctx) + } + ctx.result = res?.rows + return finishCh.publish(ctx) + } + return startCh.runStores(ctx, () => { if (abortController.signal.aborted) { const error = abortController.signal.reason || new Error('Aborted') @@ -121,10 +118,10 @@ function wrapQuery (query) { } if (newQuery.callback) { - const originalCallback = callbackResource.bind(newQuery.callback) - newQuery.callback = function (err, res) { - finish(err, res) - return originalCallback.apply(this, arguments) + const originalCallback = newQuery.callback + newQuery.callback = function (err, ...args) { + finish(err, ...args) + return finishCh.runStores(ctx, originalCallback, this, err, ...args) } } else if (newQuery.once) { newQuery @@ -139,40 +136,33 @@ function wrapQuery (query) { try { return retval - } catch (err) { - errorCh.publish(err) + } catch (error) { + ctx.error = error + errorCh.publish(ctx) } }) } } - +const finish = (ctx) => { + finishPoolQueryCh.publish(ctx) +} function wrapPoolQuery (query) { return function () { if (!startPoolQueryCh.hasSubscribers) { return query.apply(this, arguments) } - const asyncResource = new AsyncResource('bound-anonymous-fn') - const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] } + const abortController = new AbortController() - return asyncResource.runInAsyncScope(() => { - const abortController = new AbortController() - - startPoolQueryCh.publish({ - query: pgQuery, - abortController - }) - - const finish = asyncResource.bind(function () { - finishPoolQueryCh.publish() - }) + const ctx = { query: pgQuery, abortController } + return startPoolQueryCh.runStores(ctx, () => { const cb = arguments[arguments.length - 1] if (abortController.signal.aborted) { const error = abortController.signal.reason || new Error('Aborted') - finish() + finish(ctx) if (typeof cb === 'function') { cb(error) @@ -184,7 +174,7 @@ function wrapPoolQuery (query) { if (typeof cb === 'function') { arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function () { - finish() + finish(ctx) return cb.apply(this, arguments) }) } @@ -193,9 +183,9 @@ function wrapPoolQuery (query) { if (retval?.then) { retval.then(() => { - finish() + finish(ctx) }).catch(() => { - finish() + finish(ctx) }) } diff --git a/packages/datadog-instrumentations/test/body-parser.spec.js b/packages/datadog-instrumentations/test/body-parser.spec.js index 5e057f7ea8c..d5221b5563e 100644 --- a/packages/datadog-instrumentations/test/body-parser.spec.js +++ b/packages/datadog-instrumentations/test/body-parser.spec.js @@ -4,6 +4,7 @@ const dc = require('dc-polyfill') const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') const { storage } = require('../../datadog-core') +const { withVersions } = require('../../dd-trace/test/setup/mocha') withVersions('body-parser', 'body-parser', version => { describe('body parser instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/cookie-parser.spec.js b/packages/datadog-instrumentations/test/cookie-parser.spec.js index 799d434dd05..6ef0dd5907e 100644 --- a/packages/datadog-instrumentations/test/cookie-parser.spec.js +++ b/packages/datadog-instrumentations/test/cookie-parser.spec.js @@ -4,6 +4,7 @@ const { assert } = require('chai') const dc = require('dc-polyfill') const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') withVersions('cookie-parser', 'cookie-parser', version => { describe('cookie parser instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js b/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js index 3fcf981e528..2f5fcff761a 100644 --- a/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js +++ b/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js @@ -3,6 +3,8 @@ const agent = require('../../dd-trace/test/plugins/agent') const { channel } = require('dc-polyfill') const axios = require('axios') +const { withVersions } = require('../../dd-trace/test/setup/mocha') + describe('express-mongo-sanitize', () => { withVersions('express-mongo-sanitize', 'express-mongo-sanitize', version => { describe('middleware', () => { diff --git a/packages/datadog-instrumentations/test/express-session.spec.js b/packages/datadog-instrumentations/test/express-session.spec.js index 8c76075ef58..1c51eb0f74f 100644 --- a/packages/datadog-instrumentations/test/express-session.spec.js +++ b/packages/datadog-instrumentations/test/express-session.spec.js @@ -4,6 +4,7 @@ const { assert } = require('chai') const dc = require('dc-polyfill') const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') withVersions('express-session', 'express-session', version => { describe('express-session instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/express.spec.js b/packages/datadog-instrumentations/test/express.spec.js index 534bfd041e8..c2d178fa0bf 100644 --- a/packages/datadog-instrumentations/test/express.spec.js +++ b/packages/datadog-instrumentations/test/express.spec.js @@ -3,6 +3,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios') const dc = require('dc-polyfill') +const { withVersions } = require('../../dd-trace/test/setup/mocha') withVersions('express', 'express', version => { describe('express query instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/generic-pool.spec.js b/packages/datadog-instrumentations/test/generic-pool.spec.js index d479f99d14b..453d787a0dd 100644 --- a/packages/datadog-instrumentations/test/generic-pool.spec.js +++ b/packages/datadog-instrumentations/test/generic-pool.spec.js @@ -2,6 +2,7 @@ require('..') const { storage } = require('../../datadog-core') +const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Instrumentation', () => { let genericPool diff --git a/packages/datadog-instrumentations/test/helpers/promise.js b/packages/datadog-instrumentations/test/helpers/promise.js index 043b7805f02..fba8afe5ed7 100644 --- a/packages/datadog-instrumentations/test/helpers/promise.js +++ b/packages/datadog-instrumentations/test/helpers/promise.js @@ -4,6 +4,7 @@ const { expect } = require('chai') const semver = require('semver') const { storage } = require('../../../datadog-core') const agent = require('../../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') module.exports = (name, factory, versionRange) => { describe('Instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/helpers/register.spec.js b/packages/datadog-instrumentations/test/helpers/register.spec.js index d62ee78385a..8bd4d4aa09f 100644 --- a/packages/datadog-instrumentations/test/helpers/register.spec.js +++ b/packages/datadog-instrumentations/test/helpers/register.spec.js @@ -20,7 +20,6 @@ describe('register', () => { hooksMock = { '@confluentinc/kafka-javascript': { - disabled: true, fn: sinon.stub().returns('hooked') }, 'mongodb-core': { @@ -72,54 +71,6 @@ describe('register', () => { } } - it('should skip hooks that are disabled by default and process enabled hooks', () => { - loadRegisterWithEnv() - - expect(HookMock.callCount).to.equal(1) - expect(HookMock.args[0]).to.deep.include(['mongodb-core'], { internals: undefined }) - - runHookCallbacks(HookMock) - - expect(hooksMock['@confluentinc/kafka-javascript'].fn).to.not.have.been.called - expect(hooksMock['mongodb-core'].fn).to.have.been.called - }) - - it('should enable disabled hooks when DD_TRACE_[pkg]_ENABLED is true', () => { - loadRegisterWithEnv({ DD_TRACE_CONFLUENTINC_KAFKA_JAVASCRIPT_ENABLED: 'true' }) - - expect(HookMock.callCount).to.equal(2) - expect(HookMock.args[0]).to.deep.include(['@confluentinc/kafka-javascript'], { internals: undefined }) - expect(HookMock.args[1]).to.deep.include(['mongodb-core'], { internals: undefined }) - - runHookCallbacks(HookMock) - - expect(hooksMock['@confluentinc/kafka-javascript'].fn).to.have.been.called - expect(hooksMock['mongodb-core'].fn).to.have.been.called - }) - - it('should not enable disabled hooks when DD_TRACE_[pkg]_ENABLED is false', () => { - loadRegisterWithEnv({ DD_TRACE_CONFLUENTINC_KAFKA_JAVASCRIPT_ENABLED: 'false' }) - - expect(HookMock.callCount).to.equal(1) - expect(HookMock.args[0]).to.deep.include(['mongodb-core'], { internals: undefined }) - - runHookCallbacks(HookMock) - - expect(hooksMock['@confluentinc/kafka-javascript'].fn).to.not.have.been.called - expect(hooksMock['mongodb-core'].fn).to.have.been.called - }) - - it('should disable hooks that are disabled by DD_TRACE_[pkg]_ENABLED=false', () => { - loadRegisterWithEnv({ DD_TRACE_MONGODB_CORE_ENABLED: 'false' }) - - expect(HookMock.callCount).to.equal(0) - - runHookCallbacks(HookMock) - - expect(hooksMock['@confluentinc/kafka-javascript'].fn).to.not.have.been.called - expect(hooksMock['mongodb-core'].fn).to.not.have.been.called - }) - it('should disable hooks that are disabled by DD_TRACE_DISABLED_INSTRUMENTATIONS', () => { loadRegisterWithEnv({ DD_TRACE_DISABLED_INSTRUMENTATIONS: 'mongodb-core,@confluentinc/kafka-javascript' }) diff --git a/packages/datadog-instrumentations/test/knex.spec.js b/packages/datadog-instrumentations/test/knex.spec.js index 329536fb9b7..165c2004f0b 100644 --- a/packages/datadog-instrumentations/test/knex.spec.js +++ b/packages/datadog-instrumentations/test/knex.spec.js @@ -2,6 +2,7 @@ require('../src/knex') const { storage } = require('../../datadog-core') +const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Instrumentation', () => { let knex diff --git a/packages/datadog-instrumentations/test/mongoose.spec.js b/packages/datadog-instrumentations/test/mongoose.spec.js index 4982ebb8c30..6906c7836a2 100644 --- a/packages/datadog-instrumentations/test/mongoose.spec.js +++ b/packages/datadog-instrumentations/test/mongoose.spec.js @@ -2,6 +2,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const { channel } = require('../src/helpers/instrument') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const semver = require('semver') const startCh = channel('datadog:mongoose:model:filter:start') diff --git a/packages/datadog-instrumentations/test/multer.spec.js b/packages/datadog-instrumentations/test/multer.spec.js index 8bd01b5af49..63e7abd4152 100644 --- a/packages/datadog-instrumentations/test/multer.spec.js +++ b/packages/datadog-instrumentations/test/multer.spec.js @@ -4,6 +4,7 @@ const dc = require('dc-polyfill') const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') const { storage } = require('../../datadog-core') +const { withVersions } = require('../../dd-trace/test/setup/mocha') withVersions('multer', 'multer', version => { describe('multer parser instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/mysql2.spec.js b/packages/datadog-instrumentations/test/mysql2.spec.js index 428eeebeca6..f7dfb392b11 100644 --- a/packages/datadog-instrumentations/test/mysql2.spec.js +++ b/packages/datadog-instrumentations/test/mysql2.spec.js @@ -2,6 +2,7 @@ const { channel } = require('../src/helpers/instrument') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { assert } = require('chai') const semver = require('semver') const { once } = require('events') diff --git a/packages/datadog-instrumentations/test/passport-http.spec.js b/packages/datadog-instrumentations/test/passport-http.spec.js index 4d647c09fce..90d338fd876 100644 --- a/packages/datadog-instrumentations/test/passport-http.spec.js +++ b/packages/datadog-instrumentations/test/passport-http.spec.js @@ -4,6 +4,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios').create({ validateStatus: null }) const dc = require('dc-polyfill') const { storage } = require('../../datadog-core') +const { withVersions } = require('../../dd-trace/test/setup/mocha') withVersions('passport-http', 'passport-http', version => { describe('passport-http instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/passport-local.spec.js b/packages/datadog-instrumentations/test/passport-local.spec.js index 7b5795fef0f..18bdc7dba17 100644 --- a/packages/datadog-instrumentations/test/passport-local.spec.js +++ b/packages/datadog-instrumentations/test/passport-local.spec.js @@ -4,6 +4,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios').create({ validateStatus: null }) const dc = require('dc-polyfill') const { storage } = require('../../datadog-core') +const { withVersions } = require('../../dd-trace/test/setup/mocha') withVersions('passport-local', 'passport-local', version => { describe('passport-local instrumentation', () => { diff --git a/packages/datadog-instrumentations/test/passport.spec.js b/packages/datadog-instrumentations/test/passport.spec.js index 5d39a75f01b..58bea18aafa 100644 --- a/packages/datadog-instrumentations/test/passport.spec.js +++ b/packages/datadog-instrumentations/test/passport.spec.js @@ -5,6 +5,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios').create({ validateStatus: null }) const dc = require('dc-polyfill') const { storage } = require('../../datadog-core') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const users = [ { diff --git a/packages/datadog-instrumentations/test/pg.spec.js b/packages/datadog-instrumentations/test/pg.spec.js index 1273fe04404..9f203aed364 100644 --- a/packages/datadog-instrumentations/test/pg.spec.js +++ b/packages/datadog-instrumentations/test/pg.spec.js @@ -1,6 +1,7 @@ 'use strict' const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const dc = require('dc-polyfill') const { assert } = require('chai') diff --git a/packages/datadog-plugin-aerospike/src/index.js b/packages/datadog-plugin-aerospike/src/index.js index 867ba598554..b87e5b78b64 100644 --- a/packages/datadog-plugin-aerospike/src/index.js +++ b/packages/datadog-plugin-aerospike/src/index.js @@ -64,8 +64,12 @@ class AerospikePlugin extends DatabasePlugin { function getMeta (resourceName, commandArgs) { let meta = {} if (resourceName.includes('Index')) { - const [ns, set, bin, index] = commandArgs - meta = getMetaForIndex(ns, set, bin, index) + const [ns, set, bin, exp, index] = commandArgs + + // The `ext` argument was added to IndexCreate in 6.3.0 + meta = commandArgs.length > 8 + ? getMetaForIndex(ns, set, bin, index) + : getMetaForIndex(ns, set, bin, exp) } else if (resourceName === 'Query') { const { ns, set } = commandArgs[2] meta = getMetaForQuery({ ns, set }) diff --git a/packages/datadog-plugin-ai/src/index.js b/packages/datadog-plugin-ai/src/index.js new file mode 100644 index 00000000000..d0f6b1ba75a --- /dev/null +++ b/packages/datadog-plugin-ai/src/index.js @@ -0,0 +1,17 @@ +'use strict' + +const CompositePlugin = require('../../dd-trace/src/plugins/composite') +const VercelAILLMObsPlugin = require('../../dd-trace/src/llmobs/plugins/ai') +const VercelAITracingPlugin = require('./tracing') + +class VercelAIPlugin extends CompositePlugin { + static get id () { return 'ai' } + static get plugins () { + return { + llmobs: VercelAILLMObsPlugin, + tracing: VercelAITracingPlugin + } + } +} + +module.exports = VercelAIPlugin diff --git a/packages/datadog-plugin-ai/src/tracing.js b/packages/datadog-plugin-ai/src/tracing.js new file mode 100644 index 00000000000..3c5a11a915d --- /dev/null +++ b/packages/datadog-plugin-ai/src/tracing.js @@ -0,0 +1,33 @@ +'use strict' + +const TracingPlugin = require('../../dd-trace/src/plugins/tracing') +const { getModelProvider } = require('./utils') + +class VercelAITracingPlugin extends TracingPlugin { + static id = 'ai' + static prefix = 'tracing:dd-trace:vercel-ai' + + bindStart (ctx) { + const attributes = ctx.attributes + + const model = attributes['ai.model.id'] + const modelProvider = getModelProvider(attributes) + + this.startSpan(ctx.name, { + meta: { + 'resource.name': ctx.name, + 'ai.request.model': model, + 'ai.request.model_provider': modelProvider + } + }, ctx) + + return ctx.currentStore + } + + asyncEnd (ctx) { + const span = ctx.currentStore?.span + span?.finish() + } +} + +module.exports = VercelAITracingPlugin diff --git a/packages/datadog-plugin-ai/src/utils.js b/packages/datadog-plugin-ai/src/utils.js new file mode 100644 index 00000000000..1356cab21f1 --- /dev/null +++ b/packages/datadog-plugin-ai/src/utils.js @@ -0,0 +1,28 @@ +'use strict' + +const { parseModelId } = require('../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils') + +/** + * Get the model provider from the span tags or attributes. + * This is normalized to LLM Observability model provider standards. + * + * @param {Record} tags + * @returns {string} + */ +function getModelProvider (tags) { + const modelProviderTag = tags['ai.model.provider'] + const providerParts = modelProviderTag?.split('.') + const provider = providerParts?.[0] + + if (provider === 'amazon-bedrock') { + const modelId = tags['ai.model.id'] + const model = modelId && parseModelId(modelId) + return model?.modelProvider ?? provider + } + + return provider +} + +module.exports = { + getModelProvider +} diff --git a/packages/datadog-plugin-ai/test/index.spec.js b/packages/datadog-plugin-ai/test/index.spec.js new file mode 100644 index 00000000000..96dea9bc25b --- /dev/null +++ b/packages/datadog-plugin-ai/test/index.spec.js @@ -0,0 +1,312 @@ +'use strict' + +const agent = require('../../dd-trace/test/plugins/agent') +const { useEnv } = require('../../../integration-tests/helpers') +const assert = require('node:assert') +const semifies = require('semifies') +const { withVersions } = require('../../dd-trace/test/setup/mocha') + +const { NODE_MAJOR } = require('../../../version') + +// ai<4.0.2 is not supported in CommonJS with Node.js < 22 +const range = NODE_MAJOR < 22 ? '>=4.0.2' : '>=4.0.0' + +function getAiSdkOpenAiPackage (vercelAiVersion) { + return semifies(vercelAiVersion, '>=5.0.0') ? '@ai-sdk/openai' : '@ai-sdk/openai@1.3.23' +} + +describe('Plugin', () => { + useEnv({ + OPENAI_API_KEY: '' + }) + + withVersions('ai', 'ai', range, (version, _, realVersion) => { + let ai + let openai + + before(() => agent.load('ai')) + + after(() => agent.close({ ritmReset: false })) + + beforeEach(function () { + ai = require(`../../../versions/ai@${version}`).get() + + const OpenAI = require(`../../../versions/${getAiSdkOpenAiPackage(realVersion)}`).get() + openai = OpenAI.createOpenAI({ + baseURL: 'http://127.0.0.1:9126/vcr/openai', + compatibility: 'strict' + }) + }) + + it('creates a span for generateText', async () => { + const checkTraces = agent.assertSomeTraces(traces => { + const generateTextSpan = traces[0][0] + const doGenerateSpan = traces[0][1] + + assert.strictEqual(generateTextSpan.name, 'ai.generateText') + assert.strictEqual(generateTextSpan.resource, 'ai.generateText') + assert.strictEqual(generateTextSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(generateTextSpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(doGenerateSpan.name, 'ai.generateText.doGenerate') + assert.strictEqual(doGenerateSpan.resource, 'ai.generateText.doGenerate') + assert.strictEqual(doGenerateSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(doGenerateSpan.meta['ai.request.model_provider'], 'openai') + }) + + const result = await ai.generateText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'Hello, OpenAI!', + maxTokens: 100, + temperature: 0.5 + }) + + assert.ok(result.text, 'Expected result to be truthy') + + await checkTraces + }) + + it('creates a span for generateObject', async () => { + const checkTraces = agent.assertSomeTraces(traces => { + const generateObjectSpan = traces[0][0] + const doGenerateSpan = traces[0][1] + + assert.strictEqual(generateObjectSpan.name, 'ai.generateObject') + assert.strictEqual(generateObjectSpan.resource, 'ai.generateObject') + assert.strictEqual(generateObjectSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(generateObjectSpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(doGenerateSpan.name, 'ai.generateObject.doGenerate') + assert.strictEqual(doGenerateSpan.resource, 'ai.generateObject.doGenerate') + assert.strictEqual(doGenerateSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(doGenerateSpan.meta['ai.request.model_provider'], 'openai') + }) + + const schema = ai.jsonSchema({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + height: { type: 'string' } + }, + required: ['name', 'age', 'height'] + }) + + const result = await ai.generateObject({ + model: openai('gpt-4o-mini'), + schema, + prompt: 'Invent a character for a video game' + }) + + assert.ok(result.object, 'Expected result to be truthy') + + await checkTraces + }) + + it('creates a span for embed', async () => { + const checkTraces = agent.assertSomeTraces(traces => { + const embedSpan = traces[0][0] + const doEmbedSpan = traces[0][1] + + assert.strictEqual(embedSpan.name, 'ai.embed') + assert.strictEqual(embedSpan.resource, 'ai.embed') + assert.strictEqual(embedSpan.meta['ai.request.model'], 'text-embedding-ada-002') + assert.strictEqual(embedSpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(doEmbedSpan.name, 'ai.embed.doEmbed') + assert.strictEqual(doEmbedSpan.resource, 'ai.embed.doEmbed') + assert.strictEqual(doEmbedSpan.meta['ai.request.model'], 'text-embedding-ada-002') + assert.strictEqual(doEmbedSpan.meta['ai.request.model_provider'], 'openai') + }) + + const result = await ai.embed({ + model: openai.embedding('text-embedding-ada-002'), + value: 'hello world' + }) + + assert.ok(result.embedding, 'Expected result to be truthy') + + await checkTraces + }) + + it('creates a span for embedMany', async () => { + const checkTraces = agent.assertSomeTraces(traces => { + const embedManySpan = traces[0][0] + const doEmbedSpan = traces[0][1] + + assert.strictEqual(embedManySpan.name, 'ai.embedMany') + assert.strictEqual(embedManySpan.resource, 'ai.embedMany') + assert.strictEqual(embedManySpan.meta['ai.request.model'], 'text-embedding-ada-002') + assert.strictEqual(embedManySpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(doEmbedSpan.name, 'ai.embedMany.doEmbed') + assert.strictEqual(doEmbedSpan.resource, 'ai.embedMany.doEmbed') + assert.strictEqual(doEmbedSpan.meta['ai.request.model'], 'text-embedding-ada-002') + assert.strictEqual(doEmbedSpan.meta['ai.request.model_provider'], 'openai') + }) + + const result = await ai.embedMany({ + model: openai.embedding('text-embedding-ada-002'), + values: ['hello world', 'goodbye world'] + }) + + assert.ok(result.embeddings, 'Expected result to be truthy') + + await checkTraces + }) + + it('creates a span for streamText', async () => { + const checkTraces = agent.assertSomeTraces(traces => { + const streamTextSpan = traces[0][0] + const doStreamSpan = traces[0][1] + + assert.strictEqual(streamTextSpan.name, 'ai.streamText') + assert.strictEqual(streamTextSpan.resource, 'ai.streamText') + assert.strictEqual(streamTextSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(streamTextSpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(doStreamSpan.name, 'ai.streamText.doStream') + assert.strictEqual(doStreamSpan.resource, 'ai.streamText.doStream') + assert.strictEqual(doStreamSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(doStreamSpan.meta['ai.request.model_provider'], 'openai') + }) + + const result = await ai.streamText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'Hello, OpenAI!', + maxTokens: 100, + temperature: 0.5 + }) + + const textStream = result.textStream + + assert.ok(textStream, 'Expected result to be truthy') + + for await (const part of textStream) { + assert.ok(part, 'Expected part to be truthy') + } + + await checkTraces + }) + + it('creates a span for streamObject', async () => { + const checkTraces = agent.assertSomeTraces(traces => { + const streamObjectSpan = traces[0][0] + const doStreamSpan = traces[0][1] + + assert.strictEqual(streamObjectSpan.name, 'ai.streamObject') + assert.strictEqual(streamObjectSpan.resource, 'ai.streamObject') + assert.strictEqual(streamObjectSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(streamObjectSpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(doStreamSpan.name, 'ai.streamObject.doStream') + assert.strictEqual(doStreamSpan.resource, 'ai.streamObject.doStream') + assert.strictEqual(doStreamSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(doStreamSpan.meta['ai.request.model_provider'], 'openai') + }) + + const schema = ai.jsonSchema({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + height: { type: 'string' } + }, + required: ['name', 'age', 'height'] + }) + + const result = await ai.streamObject({ + model: openai('gpt-4o-mini'), + schema, + prompt: 'Invent a character for a video game' + }) + + const partialObjectStream = result.partialObjectStream + + assert.ok(partialObjectStream, 'Expected result to be truthy') + + for await (const part of partialObjectStream) { + assert.ok(part, 'Expected part to be truthy') + } + + await checkTraces + }) + + it('creates a span for a tool call', async () => { + const checkTraces = agent.assertSomeTraces(traces => { + const toolCallSpan = traces[0][0] + const doGenerateSpan = traces[0][1] + const toolCallSpan2 = traces[0][2] + const doGenerateSpan2 = traces[0][3] + + assert.strictEqual(toolCallSpan.name, 'ai.generateText') + assert.strictEqual(toolCallSpan.resource, 'ai.generateText') + assert.strictEqual(toolCallSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(toolCallSpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(doGenerateSpan.name, 'ai.generateText.doGenerate') + assert.strictEqual(doGenerateSpan.resource, 'ai.generateText.doGenerate') + assert.strictEqual(doGenerateSpan.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(doGenerateSpan.meta['ai.request.model_provider'], 'openai') + + assert.strictEqual(toolCallSpan2.name, 'ai.toolCall') + assert.strictEqual(toolCallSpan2.resource, 'ai.toolCall') + + assert.strictEqual(doGenerateSpan2.name, 'ai.generateText.doGenerate') + assert.strictEqual(doGenerateSpan2.resource, 'ai.generateText.doGenerate') + assert.strictEqual(doGenerateSpan2.meta['ai.request.model'], 'gpt-4o-mini') + assert.strictEqual(doGenerateSpan2.meta['ai.request.model_provider'], 'openai') + }) + + let tools + let maxStepsArg = {} + const toolSchema = ai.jsonSchema({ + type: 'object', + properties: { + location: { type: 'string', description: 'The location to get the weather for' } + }, + required: ['location'] + }) + if (semifies(realVersion, '>=5.0.0')) { + tools = { + weather: ai.tool({ + description: 'Get the weather in a given location', + inputSchema: toolSchema, + execute: async ({ location }) => ({ + location, + temperature: 72 + }) + }) + } + + maxStepsArg = { stopWhen: ai.stepCountIs(5) } + } else { + tools = [ai.tool({ + id: 'weather', + description: 'Get the weather in a given location', + parameters: toolSchema, + execute: async ({ location }) => ({ + location, + temperature: 72 + }) + })] + + maxStepsArg = { maxSteps: 5 } + } + + const result = await ai.generateText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'What is the weather in Tokyo?', + tools, + ...maxStepsArg, + }) + + assert.ok(result.text, 'Expected result to be truthy') + + await checkTraces + }) + }) +}) diff --git a/packages/datadog-plugin-ai/test/integration-test/client.spec.js b/packages/datadog-plugin-ai/test/integration-test/client.spec.js new file mode 100644 index 00000000000..4c0d694d647 --- /dev/null +++ b/packages/datadog-plugin-ai/test/integration-test/client.spec.js @@ -0,0 +1,73 @@ +'use strict' + +const { + FakeAgent, + createSandbox, + spawnPluginIntegrationTestProc +} = require('../../../../integration-tests/helpers') +const { assert } = require('chai') +const semifies = require('semifies') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') + +function getOpenaiVersion (realVersion) { + if (semifies(realVersion, '>=5.0.0')) { + return '2.0.0' + } + return '1.3.23' +} + +describe('esm', () => { + let agent + let proc + let sandbox + + withVersions('ai', 'ai', (version, _, realVersion) => { + before(async function () { + this.timeout(20000) + sandbox = await createSandbox([ + `ai@${version}`, + `@ai-sdk/openai@${getOpenaiVersion(realVersion)}`, + 'zod@3.25.75' + ], false, [ + './packages/datadog-plugin-ai/test/integration-test/*' + ]) + }) + + after(async () => { + await sandbox.remove() + }) + + beforeEach(async () => { + agent = await new FakeAgent().start() + }) + + afterEach(async () => { + proc?.kill() + await agent.stop() + }) + + it('is instrumented', async () => { + const res = agent.assertMessageReceived(({ headers, payload }) => { + assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) + assert.isArray(payload) + + // special check for ai spans + for (const spans of payload) { + for (const span of spans) { + if (span.name.startsWith('ai')) { + return + } + } + } + + assert.fail('No ai spans found') + }) + + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, null, { + NODE_OPTIONS: '--import dd-trace/initialize.mjs' + }) + + await res + }).timeout(20000) + }) +}) diff --git a/packages/datadog-plugin-ai/test/integration-test/server.mjs b/packages/datadog-plugin-ai/test/integration-test/server.mjs new file mode 100644 index 00000000000..8bf8d74e854 --- /dev/null +++ b/packages/datadog-plugin-ai/test/integration-test/server.mjs @@ -0,0 +1,18 @@ +import { generateText } from 'ai' +import { createOpenAI } from '@ai-sdk/openai' +import assert from 'node:assert' + +const openai = createOpenAI({ + baseURL: 'http://127.0.0.1:9126/vcr/openai', + apiKey: '' +}) + +const result = await generateText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'Hello, OpenAI!', + maxTokens: 100, + temperature: 0.5 +}) + +assert.ok(result.text, 'Expected result to be truthy') diff --git a/packages/datadog-plugin-amqp10/test/integration-test/client.spec.js b/packages/datadog-plugin-amqp10/test/integration-test/client.spec.js index 8deadf31385..19267627646 100644 --- a/packages/datadog-plugin-amqp10/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-amqp10/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-amqplib/test/integration-test/client.spec.js b/packages/datadog-plugin-amqplib/test/integration-test/client.spec.js index f7fda5fa651..849fc4a454a 100644 --- a/packages/datadog-plugin-amqplib/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-amqplib/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-avsc/test/index.spec.js b/packages/datadog-plugin-avsc/test/index.spec.js index b3a6db0c1f1..ea02234fbb3 100644 --- a/packages/datadog-plugin-avsc/test/index.spec.js +++ b/packages/datadog-plugin-avsc/test/index.spec.js @@ -1,9 +1,10 @@ 'use strict' const fs = require('fs') +const path = require('path') const { expect } = require('chai') const agent = require('../../dd-trace/test/plugins/agent') -const path = require('path') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { SCHEMA_DEFINITION, SCHEMA_ID, diff --git a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js index 8262763e328..be77f01ea29 100644 --- a/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/aws-sdk.spec.js @@ -2,6 +2,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const { setup, sort } = require('./spec_helpers') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const semver = require('semver') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') diff --git a/packages/datadog-plugin-aws-sdk/test/bedrockruntime.spec.js b/packages/datadog-plugin-aws-sdk/test/bedrockruntime.spec.js index f7795436b05..ab5c945c647 100644 --- a/packages/datadog-plugin-aws-sdk/test/bedrockruntime.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/bedrockruntime.spec.js @@ -4,6 +4,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const nock = require('nock') const { setup } = require('./spec_helpers') const { models } = require('./fixtures/bedrockruntime') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const serviceName = 'bedrock-service-name-test' diff --git a/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js b/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js index 7240dbfe722..a8cd2ae1115 100644 --- a/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/dynamodb.spec.js @@ -1,6 +1,7 @@ 'use strict' const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { setup } = require('./spec_helpers') const axios = require('axios') const { DYNAMODB_PTR_KIND, SPAN_POINTER_DIRECTION } = require('../../dd-trace/src/constants') diff --git a/packages/datadog-plugin-aws-sdk/test/eventbridge.spec.js b/packages/datadog-plugin-aws-sdk/test/eventbridge.spec.js index 712e7cab686..2ca38c42571 100644 --- a/packages/datadog-plugin-aws-sdk/test/eventbridge.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/eventbridge.spec.js @@ -2,6 +2,7 @@ 'use strict' const EventBridge = require('../src/services/eventbridge') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const tracer = require('../../dd-trace') const { randomBytes } = require('crypto') diff --git a/packages/datadog-plugin-aws-sdk/test/integration-test/client.spec.js b/packages/datadog-plugin-aws-sdk/test/integration-test/client.spec.js index e077c0b64b2..4e93c23fa77 100644 --- a/packages/datadog-plugin-aws-sdk/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-aws-sdk/test/serverless-peer-service.spec.js b/packages/datadog-plugin-aws-sdk/test/serverless-peer-service.spec.js index 045b2e7d998..458e74d711c 100644 --- a/packages/datadog-plugin-aws-sdk/test/serverless-peer-service.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/serverless-peer-service.spec.js @@ -4,6 +4,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const helpers = require('./kinesis_helpers') const { promisify } = require('util') const { setup } = require('./spec_helpers') +const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Plugin', () => { describe('Serverless', function () { diff --git a/packages/datadog-plugin-aws-sdk/test/stepfunctions.spec.js b/packages/datadog-plugin-aws-sdk/test/stepfunctions.spec.js index 2198704e000..93164d5eb1c 100644 --- a/packages/datadog-plugin-aws-sdk/test/stepfunctions.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/stepfunctions.spec.js @@ -2,6 +2,7 @@ const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { setup } = require('./spec_helpers') const helloWorldSMD = { diff --git a/packages/datadog-plugin-azure-functions/test/integration-test/client.spec.js b/packages/datadog-plugin-azure-functions/test/integration-test/client.spec.js index 584e454d924..d004bfe2c48 100644 --- a/packages/datadog-plugin-azure-functions/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-azure-functions/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { createSandbox, curlAndAssertMessage } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { spawn } = require('child_process') const { assert } = require('chai') const { NODE_MAJOR } = require('../../../../version') @@ -22,7 +23,6 @@ describe('esm', () => { this.timeout(120_000) sandbox = await createSandbox([ `@azure/functions@${version}`, - 'azure-functions-core-tools@4.1.0', '@azure/service-bus@7.9.2' ], false, @@ -45,7 +45,7 @@ describe('esm', () => { it('is instrumented', async () => { const envArgs = { - PATH: `${sandbox.folder}/node_modules/azure-functions-core-tools/bin:${process.env.PATH}` + PATH: process.env.PATH } proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'func', ['start'], agent.port, undefined, envArgs) @@ -61,7 +61,7 @@ describe('esm', () => { it('propagates context to child http requests', async () => { const envArgs = { - PATH: `${sandbox.folder}/node_modules/azure-functions-core-tools/bin:${process.env.PATH}` + PATH: process.env.PATH } proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'func', ['start'], agent.port, undefined, envArgs) @@ -73,7 +73,7 @@ describe('esm', () => { it('propagates context through a service bus queue', async () => { const envArgs = { - PATH: `${sandbox.folder}/node_modules/azure-functions-core-tools/bin:${process.env.PATH}` + PATH: process.env.PATH } proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'func', ['start'], agent.port, undefined, envArgs) @@ -91,7 +91,7 @@ describe('esm', () => { it('propagates context through a service bus topic', async () => { const envArgs = { - PATH: `${sandbox.folder}/node_modules/azure-functions-core-tools/bin:${process.env.PATH}` + PATH: process.env.PATH } proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'func', ['start'], agent.port, undefined, envArgs) diff --git a/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js b/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js index 81ad67a366e..5a51bfc9f61 100644 --- a/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-azure-service-bus/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-bunyan/test/index.spec.js b/packages/datadog-plugin-bunyan/test/index.spec.js index 4e3276c2150..c06ad63f657 100644 --- a/packages/datadog-plugin-bunyan/test/index.spec.js +++ b/packages/datadog-plugin-bunyan/test/index.spec.js @@ -2,6 +2,7 @@ const Writable = require('stream').Writable const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Plugin', () => { let logger diff --git a/packages/datadog-plugin-bunyan/test/integration-test/client.spec.js b/packages/datadog-plugin-bunyan/test/integration-test/client.spec.js index 5ce5d8884fa..2777b5b84e2 100644 --- a/packages/datadog-plugin-bunyan/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-bunyan/test/integration-test/client.spec.js @@ -5,6 +5,7 @@ const { createSandbox, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { expect } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-cassandra-driver/test/integration-test/client.spec.js b/packages/datadog-plugin-cassandra-driver/test/integration-test/client.spec.js index b23376bb3df..7ebeb665d31 100644 --- a/packages/datadog-plugin-cassandra-driver/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-cassandra-driver/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-confluentinc-kafka-javascript/test/index.spec.js b/packages/datadog-plugin-confluentinc-kafka-javascript/test/index.spec.js index 026b754703e..a866537167a 100644 --- a/packages/datadog-plugin-confluentinc-kafka-javascript/test/index.spec.js +++ b/packages/datadog-plugin-confluentinc-kafka-javascript/test/index.spec.js @@ -6,6 +6,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const { expectSomeSpan, withDefaults } = require('../../dd-trace/test/plugins/helpers') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const { expectedSchema } = require('./naming') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const DataStreamsContext = require('../../dd-trace/src/datastreams/context') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') diff --git a/packages/datadog-plugin-confluentinc-kafka-javascript/test/integration-test/client.spec.js b/packages/datadog-plugin-confluentinc-kafka-javascript/test/integration-test/client.spec.js index f8638408be3..e0320519df3 100644 --- a/packages/datadog-plugin-confluentinc-kafka-javascript/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-confluentinc-kafka-javascript/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-connect/test/index.spec.js b/packages/datadog-plugin-connect/test/index.spec.js index 7be08a89c51..a4480850bde 100644 --- a/packages/datadog-plugin-connect/test/index.spec.js +++ b/packages/datadog-plugin-connect/test/index.spec.js @@ -3,6 +3,7 @@ const axios = require('axios') const http = require('http') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { AsyncLocalStorage } = require('async_hooks') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') diff --git a/packages/datadog-plugin-connect/test/integration-test/client.spec.js b/packages/datadog-plugin-connect/test/integration-test/client.spec.js index a045ba8bf09..541db6e2cc5 100644 --- a/packages/datadog-plugin-connect/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-connect/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-couchbase/src/index.js b/packages/datadog-plugin-couchbase/src/index.js index 868b17e06f6..bfafe17727c 100644 --- a/packages/datadog-plugin-couchbase/src/index.js +++ b/packages/datadog-plugin-couchbase/src/index.js @@ -7,13 +7,15 @@ class CouchBasePlugin extends StoragePlugin { static id = 'couchbase' static peerServicePrecursors = ['db.couchbase.seed.nodes'] - addSubs (func, start) { - this.addSub(`apm:couchbase:${func}:start`, start) - this.addSub(`apm:couchbase:${func}:error`, error => this.addError(error)) - this.addSub(`apm:couchbase:${func}:finish`, message => this.finish(message)) + addBinds (func, start) { + this.addBind(`apm:couchbase:${func}:start`, start) + this.addSub(`apm:couchbase:${func}:error`, ({ error }) => this.addError(error)) + this.addSub(`apm:couchbase:${func}:finish`, ctx => this.finish(ctx)) + this.addBind(`apm:couchbase:${func}:callback:start`, callbackStart) + this.addBind(`apm:couchbase:${func}:callback:finish`, callbackFinish) } - startSpan (operation, customTags, store, { bucket, collection, seedNodes }) { + startSpan (operation, customTags, { bucket, collection, seedNodes }, ctx) { const tags = { 'db.type': 'couchbase', component: 'couchbase', @@ -34,26 +36,34 @@ class CouchBasePlugin extends StoragePlugin { { service: this.serviceName({ pluginConfig: this.config }), meta: tags - } + }, + ctx ) } constructor (...args) { super(...args) - this.addSubs('query', ({ resource, bucket, seedNodes }) => { - const store = storage('legacy').getStore() - const span = this.startSpan( - 'query', { + this.addBinds('query', (ctx) => { + const { resource, bucket, seedNodes } = ctx + + this.startSpan( + 'query', + { 'span.type': 'sql', 'resource.name': resource, 'span.kind': this.constructor.kind }, - store, - { bucket, seedNodes } + { bucket, seedNodes }, + ctx ) - this.enter(span, store) + + return ctx.currentStore }) + this.addBind('apm:couchbase:bucket:maybeInvoke:callback:start', callbackStart) + this.addBind('apm:couchbase:bucket:maybeInvoke:callback:finish', callbackFinish) + this.addBind('apm:couchbase:cluster:maybeInvoke:callback:start', callbackStart) + this.addBind('apm:couchbase:cluster:maybeInvoke:callback:finish', callbackFinish) this._addCommandSubs('upsert') this._addCommandSubs('insert') @@ -63,12 +73,22 @@ class CouchBasePlugin extends StoragePlugin { } _addCommandSubs (name) { - this.addSubs(name, ({ bucket, collection, seedNodes }) => { - const store = storage('legacy').getStore() - const span = this.startSpan(name, {}, store, { bucket, collection, seedNodes }) - this.enter(span, store) + this.addBinds(name, (ctx) => { + const { bucket, collection, seedNodes } = ctx + + this.startSpan(name, {}, { bucket, collection, seedNodes }, ctx) + return ctx.currentStore }) } } +function callbackStart (ctx) { + ctx.parentStore = storage('legacy').getStore() + return ctx.parentStore +} + +function callbackFinish (ctx) { + return ctx.parentStore +} + module.exports = CouchBasePlugin diff --git a/packages/datadog-plugin-couchbase/test/integration-test/client.spec.js b/packages/datadog-plugin-couchbase/test/integration-test/client.spec.js index e37506eb3bf..b0ec4f9c669 100644 --- a/packages/datadog-plugin-couchbase/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-couchbase/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-cucumber/test/index.spec.js b/packages/datadog-plugin-cucumber/test/index.spec.js index 9f03ba8adeb..df6cee53901 100644 --- a/packages/datadog-plugin-cucumber/test/index.spec.js +++ b/packages/datadog-plugin-cucumber/test/index.spec.js @@ -9,6 +9,7 @@ const agent = require('../../dd-trace/test/plugins/agent') const { ORIGIN_KEY, COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants') const { SAMPLING_PRIORITY } = require('../../../ext/tags') const { AUTO_KEEP } = require('../../../ext/priority') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { TEST_FRAMEWORK, TEST_TYPE, diff --git a/packages/datadog-plugin-cypress/test/index.spec.js b/packages/datadog-plugin-cypress/test/index.spec.js index ece6ce0b276..820e30998e9 100644 --- a/packages/datadog-plugin-cypress/test/index.spec.js +++ b/packages/datadog-plugin-cypress/test/index.spec.js @@ -19,6 +19,7 @@ const { TEST_CODE_OWNERS, LIBRARY_VERSION } = require('../../dd-trace/src/plugins/util/test') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { version: ddTraceVersion } = require('../../../package.json') diff --git a/packages/datadog-plugin-elasticsearch/test/integration-test/client.spec.js b/packages/datadog-plugin-elasticsearch/test/integration-test/client.spec.js index 9f64b0c7c27..2c2e6202635 100644 --- a/packages/datadog-plugin-elasticsearch/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-elasticsearch/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-express/test/code_origin.spec.js b/packages/datadog-plugin-express/test/code_origin.spec.js index 32d086abf46..5f594647392 100644 --- a/packages/datadog-plugin-express/test/code_origin.spec.js +++ b/packages/datadog-plugin-express/test/code_origin.spec.js @@ -4,6 +4,7 @@ const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') const { assertCodeOriginFromTraces } = require('../../datadog-code-origin/test/helpers') const { getNextLineNumber } = require('../../dd-trace/test/plugins/helpers') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const host = 'localhost' diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 62f9f636aef..d50b7dfda4b 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -6,6 +6,7 @@ const semver = require('semver') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') const plugin = require('../src') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const sort = spans => spans.sort((a, b) => a.start.toString() >= b.start.toString() ? 1 : -1) diff --git a/packages/datadog-plugin-express/test/integration-test/client.spec.js b/packages/datadog-plugin-express/test/integration-test/client.spec.js index af81f39dcd3..f9a3339b608 100644 --- a/packages/datadog-plugin-express/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-express/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { curlAndAssertMessage, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') const semver = require('semver') diff --git a/packages/datadog-plugin-fastify/test/integration-test/client.spec.js b/packages/datadog-plugin-fastify/test/integration-test/client.spec.js index d61bb3c7555..13e0630ed2c 100644 --- a/packages/datadog-plugin-fastify/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-fastify/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-google-cloud-pubsub/test/integration-test/client.spec.js b/packages/datadog-plugin-google-cloud-pubsub/test/integration-test/client.spec.js index 0effff0795b..3e4cb6bc085 100644 --- a/packages/datadog-plugin-google-cloud-pubsub/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-google-cloud-pubsub/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-google-cloud-vertexai/test/index.spec.js b/packages/datadog-plugin-google-cloud-vertexai/test/index.spec.js index b28d32b126d..b19bd97d8ca 100644 --- a/packages/datadog-plugin-google-cloud-vertexai/test/index.spec.js +++ b/packages/datadog-plugin-google-cloud-vertexai/test/index.spec.js @@ -1,6 +1,7 @@ 'use strict' const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const sinon = require('sinon') const fs = require('node:fs') const path = require('node:path') diff --git a/packages/datadog-plugin-google-cloud-vertexai/test/integration-test/client.spec.js b/packages/datadog-plugin-google-cloud-vertexai/test/integration-test/client.spec.js index 4b4d5a83fc1..899e7d110db 100644 --- a/packages/datadog-plugin-google-cloud-vertexai/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-google-cloud-vertexai/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-graphql/test/integration-test/client.spec.js b/packages/datadog-plugin-graphql/test/integration-test/client.spec.js index d0a4b0e42d3..2a095b4b753 100644 --- a/packages/datadog-plugin-graphql/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-graphql/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-grpc/test/integration-test/client.spec.js b/packages/datadog-plugin-grpc/test/integration-test/client.spec.js index c9a31f5c65e..42f0987436d 100644 --- a/packages/datadog-plugin-grpc/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-grpc/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-hapi/test/index.spec.js b/packages/datadog-plugin-hapi/test/index.spec.js index d4a8495fe9c..467523ffe72 100644 --- a/packages/datadog-plugin-hapi/test/index.spec.js +++ b/packages/datadog-plugin-hapi/test/index.spec.js @@ -3,6 +3,7 @@ const axios = require('axios') const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const { AsyncLocalStorage } = require('async_hooks') diff --git a/packages/datadog-plugin-hapi/test/integration-test/client.spec.js b/packages/datadog-plugin-hapi/test/integration-test/client.spec.js index 3105f810458..953474f30a8 100644 --- a/packages/datadog-plugin-hapi/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-hapi/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-hono/test/index.spec.js b/packages/datadog-plugin-hono/test/index.spec.js index d0f171df1f9..55ac6fc2b41 100644 --- a/packages/datadog-plugin-hono/test/index.spec.js +++ b/packages/datadog-plugin-hono/test/index.spec.js @@ -8,6 +8,7 @@ const { ERROR_MESSAGE, ERROR_STACK } = require('../../dd-trace/src/constants') +const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Plugin', () => { let tracer diff --git a/packages/datadog-plugin-hono/test/integration-test/client.spec.js b/packages/datadog-plugin-hono/test/integration-test/client.spec.js index e1b5456d430..b9d71d7cbe4 100644 --- a/packages/datadog-plugin-hono/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-hono/test/integration-test/client.spec.js @@ -7,16 +7,17 @@ const { spawnPluginIntegrationTestProc, assertObjectContains, } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') -describe('esm', () => { +describe('esm integration test', () => { let agent let proc let sandbox - withVersions('hono', 'hono', version => { + withVersions('hono', 'hono', (range, _moduleName_, version) => { before(async function () { this.timeout(50000) - sandbox = await createSandbox([`'hono@${version}'`, '@hono/node-server@1.15.0'], false, + sandbox = await createSandbox([`'hono@${range}'`, '@hono/node-server@1.15.0'], false, ['./packages/datadog-plugin-hono/test/integration-test/*']) }) @@ -35,13 +36,25 @@ describe('esm', () => { }) it('is instrumented', async () => { - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, undefined, { + VERSION: version + }) proc.url += '/hello' return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { assertObjectContains(headers, { host: `127.0.0.1:${agent.port}` }) - // TODO: Fix the resource! It should be 'GET /hello' - // This seems to be a generic ESM issue, also e.g., on express. + assertObjectContains(payload, [[{ name: 'hono.request', resource: 'GET /hello' }]]) + }) + }).timeout(50000) + + it('receives missing route trace', async () => { + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port, undefined, { + VERSION: version + }) + proc.url += '/missing' + + return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { + assertObjectContains(headers, { host: `127.0.0.1:${agent.port}` }) assertObjectContains(payload, [[{ name: 'hono.request', resource: 'GET' }]]) }) }).timeout(50000) diff --git a/packages/datadog-plugin-hono/test/integration-test/server.mjs b/packages/datadog-plugin-hono/test/integration-test/server.mjs index 8630dd2ac87..b8ec620436e 100644 --- a/packages/datadog-plugin-hono/test/integration-test/server.mjs +++ b/packages/datadog-plugin-hono/test/integration-test/server.mjs @@ -2,10 +2,34 @@ import 'dd-trace/init.js' import { Hono } from 'hono' import { serve } from '@hono/node-server' +import process from 'node:process' + const app = new Hono() +const version = process.env.VERSION.split('.').map(Number) + +const hasCombine = version[0] > 4 || version[0] === 4 && version[1] >= 5 +const response = 'green energy\n' + +if (hasCombine) { + const { every } = await import('hono/combine') + app.use(every(async (context, next) => { + context.set('response', response) + return next() + })) +} else { + app.use(async (context, next) => { + context.set('response', response) + return next() + }) +} + app.get('/hello', (c) => { - return c.text('green energy\n') + const res = c.get('response') + if (res !== response) { + throw new Error(`Expected response to be "${response}", got "${res}"`) + } + return c.text(res) }) serve({ diff --git a/packages/datadog-plugin-ioredis/test/integration-test/client.spec.js b/packages/datadog-plugin-ioredis/test/integration-test/client.spec.js index a328b846952..ed52f073e36 100644 --- a/packages/datadog-plugin-ioredis/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-ioredis/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-iovalkey/test/integration-test/client.spec.js b/packages/datadog-plugin-iovalkey/test/integration-test/client.spec.js index 42a682ca844..26172421d66 100644 --- a/packages/datadog-plugin-iovalkey/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-iovalkey/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-jest/test/circus.spec.js b/packages/datadog-plugin-jest/test/circus.spec.js index b01e217b074..2339ce3f549 100644 --- a/packages/datadog-plugin-jest/test/circus.spec.js +++ b/packages/datadog-plugin-jest/test/circus.spec.js @@ -7,6 +7,7 @@ const semver = require('semver') const { ORIGIN_KEY, COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { TEST_FRAMEWORK, TEST_TYPE, diff --git a/packages/datadog-plugin-kafkajs/test/integration-test/client.spec.js b/packages/datadog-plugin-kafkajs/test/integration-test/client.spec.js index af231c00915..53561ae19a8 100644 --- a/packages/datadog-plugin-kafkajs/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-kafkajs/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-koa/test/index.spec.js b/packages/datadog-plugin-koa/test/index.spec.js index c008a844940..b683192992a 100644 --- a/packages/datadog-plugin-koa/test/index.spec.js +++ b/packages/datadog-plugin-koa/test/index.spec.js @@ -5,6 +5,7 @@ const axios = require('axios') const semver = require('semver') const { ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const sort = spans => spans.sort((a, b) => a.start.toString() >= b.start.toString() ? 1 : -1) diff --git a/packages/datadog-plugin-koa/test/integration-test/client.spec.js b/packages/datadog-plugin-koa/test/integration-test/client.spec.js index 2216dd6129e..d1f1a430b4e 100644 --- a/packages/datadog-plugin-koa/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-koa/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-langchain/test/index.spec.js b/packages/datadog-plugin-langchain/test/index.spec.js index 0386f17bf37..b8a908353ba 100644 --- a/packages/datadog-plugin-langchain/test/index.spec.js +++ b/packages/datadog-plugin-langchain/test/index.spec.js @@ -3,43 +3,10 @@ const { useEnv } = require('../../../integration-tests/helpers') const agent = require('../../dd-trace/test/plugins/agent') const iastFilter = require('../../dd-trace/src/appsec/iast/taint-tracking/filter') - -const nock = require('nock') -const semver = require('semver') -function stubCall ({ base = '', path = '', code = 200, response = {} }) { - const responses = Array.isArray(response) ? response : [response] - const times = responses.length - nock(base).post(path).times(times).reply(() => { - return [code, responses.shift()] - }) -} -const openAiBaseCompletionInfo = { base: 'https://api.openai.com', path: '/v1/completions' } -const openAiBaseChatInfo = { base: 'https://api.openai.com', path: '/v1/chat/completions' } -const openAiBaseEmbeddingInfo = { base: 'https://api.openai.com', path: '/v1/embeddings' } +const { withVersions } = require('../../dd-trace/test/setup/mocha') const isDdTrace = iastFilter.isDdTrace -function stubSingleEmbedding (langchainOpenaiOpenAiVersion) { - if (semver.satisfies(langchainOpenaiOpenAiVersion, '>=4.91.0')) { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: require('./fixtures/single-embedding.json') - }) - } else { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: { - object: 'list', - data: [{ - object: 'embedding', - index: 0, - embedding: Array(1536).fill(0) - }] - } - }) - } -} - describe('Plugin', () => { let langchainOpenai let langchainAnthropic @@ -51,25 +18,63 @@ describe('Plugin', () => { let langchainRunnables let langchainTools let MemoryVectorStore - /** - * In OpenAI 4.91.0, the default response format for embeddings was changed from `float` to `base64`. - * We do not have control in @langchain/openai embeddings to change this for an individual call, - * so we need to check the version and stub the response accordingly. If the OpenAI version installed with - * @langchain/openai is less than 4.91.0, we stub the response to be a float array of zeros. - * If it is 4.91.0 or greater, we stub with a pre-recorded fixture of a 1536 base64 encoded embedding. - */ - let langchainOpenaiOpenAiVersion - - // so we can verify it gets tagged properly + useEnv({ OPENAI_API_KEY: '', ANTHROPIC_API_KEY: '', GOOGLE_API_KEY: '' }) + function getLangChainOpenAiClient (type = 'llm', options = {}) { + Object.assign(options, { + configuration: { + baseURL: 'http://127.0.0.1:9126/vcr/openai' + } + }) + + if (type === 'llm') { + return new langchainOpenai.OpenAI(options) + } + + if (type === 'chat') { + return new langchainOpenai.ChatOpenAI(options) + } + + if (type === 'embedding') { + return new langchainOpenai.OpenAIEmbeddings(options) + } + + throw new Error(`Invalid type: ${type}`) + } + + function getLangChainAnthropicClient (type = 'chat', options = {}) { + Object.assign(options, { + clientOptions: { + baseURL: 'http://127.0.0.1:9126/vcr/anthropic' + } + }) + + if (type === 'chat') { + return new langchainAnthropic.ChatAnthropic(options) + } + + throw new Error(`Invalid type: ${type}`) + } + + function getLangChainGoogleGenAIClient (type = 'embedding', options = {}) { + Object.assign(options, { + baseUrl: 'http://127.0.0.1:9126/vcr/genai' + }) + + if (type === 'embedding') { + return new langchainGoogleGenAI.GoogleGenerativeAIEmbeddings(options) + } + + throw new Error(`Invalid type: ${type}`) + } + describe('langchain', () => { - // TODO(sabrenner): remove this once we have the more robust mocking merged - withVersions('langchain', ['@langchain/core'], '<0.3.60', version => { + withVersions('langchain', ['@langchain/core'], (version, _, realVersion) => { before(() => { iastFilter.isDdTrace = file => { if (file.includes('dd-trace-js/versions/')) { @@ -109,21 +114,10 @@ describe('Plugin', () => { MemoryVectorStore = require(`../../../versions/@langchain/core@${version}`) .get('langchain/vectorstores/memory') .MemoryVectorStore - - langchainOpenaiOpenAiVersion = - require(`../../../versions/langchain@${version}`) - .get('openai/version') - .VERSION - }) - - afterEach(() => { - nock.cleanAll() }) describe('llm', () => { it('does not tag output on error', async () => { - nock('https://api.openai.com').post('/v1/completions').reply(403) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -141,7 +135,9 @@ describe('Plugin', () => { }) try { - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct', maxRetries: 0 }) + const llm = getLangChainOpenAiClient('llm', + { model: 'text-embedding-3-small', maxRetries: 0 } + ) // use this bad model (embedding model not compatible) await llm.generate(['what is 2 + 2?']) } catch {} @@ -149,21 +145,7 @@ describe('Plugin', () => { }) it('instruments a langchain llm call for a single prompt', async () => { - stubCall({ - ...openAiBaseCompletionInfo, - response: { - model: 'gpt-3.5-turbo-instruct', - choices: [{ - text: 'The answer is 4', - index: 0, - logprobs: null, - finish_reason: 'length' - }], - usage: { prompt_tokens: 8, completion_tokens: 12, otal_tokens: 20 } - } - }) - - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct' }) + const llm = getLangChainOpenAiClient('llm', { model: 'gpt-3.5-turbo-instruct' }) const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -179,31 +161,12 @@ describe('Plugin', () => { const result = await llm.generate(['what is 2 + 2?']) - expect(result.generations[0][0].text).to.equal('The answer is 4') + expect(result.generations[0][0].text).to.exist await checkTraces }) it('instruments a langchain openai llm call for multiple prompts', async () => { - stubCall({ - ...openAiBaseCompletionInfo, - response: { - model: 'gpt-3.5-turbo-instruct', - choices: [{ - text: 'The answer is 4', - index: 0, - logprobs: null, - finish_reason: 'length' - }, { - text: 'The circumference of the earth is 24,901 miles', - index: 1, - logprobs: null, - finish_reason: 'length' - }], - usage: { prompt_tokens: 8, completion_tokens: 12, otal_tokens: 20 } - } - }) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -212,36 +175,16 @@ describe('Plugin', () => { expect(span.meta).to.have.property('langchain.request.model', 'gpt-3.5-turbo-instruct') }) - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct' }) + const llm = getLangChainOpenAiClient('llm', { model: 'gpt-3.5-turbo-instruct' }) const result = await llm.generate(['what is 2 + 2?', 'what is the circumference of the earth?']) - expect(result.generations[0][0].text).to.equal('The answer is 4') - expect(result.generations[1][0].text).to.equal('The circumference of the earth is 24,901 miles') + expect(result.generations[0][0].text).to.exist + expect(result.generations[1][0].text).to.exist await checkTraces }) it('instruments a langchain openai llm call for a single prompt and multiple responses', async () => { - // it should only use the first choice - stubCall({ - ...openAiBaseCompletionInfo, - response: { - model: 'gpt-3.5-turbo-instruct', - choices: [{ - text: 'The answer is 4', - index: 0, - logprobs: null, - finish_reason: 'length' - }, { - text: '2 + 2 = 4', - index: 1, - logprobs: null, - finish_reason: 'length' - }], - usage: { prompt_tokens: 8, completion_tokens: 12, otal_tokens: 20 } - } - }) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -251,11 +194,11 @@ describe('Plugin', () => { expect(span.meta).to.have.property('langchain.request.model', 'gpt-3.5-turbo-instruct') }) - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct', n: 2 }) + const llm = getLangChainOpenAiClient('llm', { model: 'gpt-3.5-turbo-instruct', n: 2 }) const result = await llm.generate(['what is 2 + 2?']) - expect(result.generations[0][0].text).to.equal('The answer is 4') - expect(result.generations[0][1].text).to.equal('2 + 2 = 4') + expect(result.generations[0][0].text).to.exist + expect(result.generations[0][1].text).to.exist await checkTraces }) @@ -263,8 +206,6 @@ describe('Plugin', () => { describe('chat model', () => { it('does not tag output on error', async () => { - nock('https://api.openai.com').post('/v1/chat/completions').reply(403) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -281,7 +222,7 @@ describe('Plugin', () => { }) try { - const chatModel = new langchainOpenai.ChatOpenAI({ model: 'gpt-4', maxRetries: 0 }) + const chatModel = getLangChainOpenAiClient('chat', { model: 'gpt-3.5-turbo-instruct', maxRetries: 0 }) await chatModel.invoke('Hello!') } catch {} @@ -289,26 +230,6 @@ describe('Plugin', () => { }) it('instruments a langchain openai chat model call for a single string prompt', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Hello! How can I assist you today?' - }, - finish_reason: 'length', - index: 0 - }] - } - }) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -322,35 +243,15 @@ describe('Plugin', () => { expect(span.meta).to.have.property('langchain.request.type', 'chat_model') }) - const chatModel = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const chatModel = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const result = await chatModel.invoke('Hello!') - expect(result.content).to.equal('Hello! How can I assist you today?') + expect(result.content).to.exist await checkTraces }) it('instruments a langchain openai chat model call for a JSON message input', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Hi!' - }, - finish_reason: 'length', - index: 0 - }] - } - }) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -360,39 +261,19 @@ describe('Plugin', () => { expect(span.meta).to.have.property('langchain.request.model', 'gpt-4') }) - const chatModel = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const chatModel = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const messages = [ { role: 'system', content: 'You only respond with one word answers' }, { role: 'human', content: 'Hello!' } ] const result = await chatModel.invoke(messages) - expect(result.content).to.equal('Hi!') + expect(result.content).to.exist await checkTraces }) it('instruments a langchain openai chat model call for a BaseMessage-like input', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Hi!' - }, - finish_reason: 'length', - index: 0 - }] - } - }) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -402,44 +283,19 @@ describe('Plugin', () => { expect(span.meta).to.have.property('langchain.request.model', 'gpt-4') }) - const chatModel = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const chatModel = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const messages = [ new langchainMessages.SystemMessage('You only respond with one word answers'), new langchainMessages.HumanMessage('Hello!') ] const result = await chatModel.invoke(messages) - expect(result.content).to.equal('Hi!') + expect(result.content).to.exist await checkTraces }) it('instruments a langchain openai chat model call with tool calls', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - model: 'gpt-4', - choices: [{ - message: { - role: 'assistant', - content: null, - tool_calls: [ - { - id: 'tool-1', - type: 'function', - function: { - name: 'extract_fictional_info', - arguments: '{"name":"SpongeBob","origin":"Bikini Bottom"}' - } - } - ] - }, - finish_reason: 'tool_calls', - index: 0 - }] - } - }) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -451,19 +307,23 @@ describe('Plugin', () => { const tools = [ { - name: 'extract_fictional_info', - description: 'Get the fictional information from the body of the input text', - parameters: { - type: 'object', - properties: { - name: { type: 'string', description: 'Name of the character' }, - origin: { type: 'string', description: 'Where they live' } + type: 'function', + function: { + name: 'extract_fictional_info', + description: 'Get the fictional information from the body of the input text', + parameters: { + type: 'object', + properties: { + name: { type: 'string', description: 'Name of the character' }, + origin: { type: 'string', description: 'Where they live' } + } } } } ] - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) + const modelWithTools = model.bindTools(tools) const result = await modelWithTools.invoke('My name is SpongeBob and I live in Bikini Bottom.') @@ -474,23 +334,6 @@ describe('Plugin', () => { }) it('instruments a langchain anthropic chat model call', async () => { - stubCall({ - base: 'https://api.anthropic.com', - path: '/v1/messages', - response: { - id: 'msg_01NE2EJQcjscRyLbyercys6p', - type: 'message', - role: 'assistant', - model: 'claude-3-opus-20240229', - content: [ - { type: 'text', text: 'Hello!' } - ], - stop_reason: 'end_turn', - stop_sequence: null, - usage: { input_tokens: 11, output_tokens: 6 } - } - }) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -504,10 +347,10 @@ describe('Plugin', () => { expect(span.meta).to.have.property('langchain.request.type', 'chat_model') }) - const chatModel = new langchainAnthropic.ChatAnthropic({ model: 'claude-3-opus-20240229' }) + const chatModel = getLangChainAnthropicClient('chat', { modelName: 'claude-3-5-sonnet-20241022' }) const result = await chatModel.invoke('Hello!') - expect(result.content).to.equal('Hello!') + expect(result.content).to.exist await checkTraces }) @@ -515,8 +358,6 @@ describe('Plugin', () => { describe('chain', () => { it('does not tag output on error', async () => { - nock('https://api.openai.com').post('/v1/chat/completions').reply(403) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(2) @@ -534,7 +375,8 @@ describe('Plugin', () => { }) try { - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4', maxRetries: 0 }) + // use a bad model + const model = getLangChainOpenAiClient('chat', { model: 'gpt-3.5-turbo-instruct', maxRetries: 0 }) const parser = new langchainOutputParsers.StringOutputParser() const chain = model.pipe(parser) @@ -546,26 +388,6 @@ describe('Plugin', () => { }) it('instruments a langchain chain with a single openai chat model call', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Hi!' - }, - finish_reason: 'length', - index: 0 - }] - } - }) - const checkTraces = agent .assertSomeTraces(traces => { const spans = traces[0] @@ -581,7 +403,7 @@ describe('Plugin', () => { expect(chainSpan.meta).to.have.property('langchain.request.type', 'chain') }) - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const parser = new langchainOutputParsers.StringOutputParser() const chain = model.pipe(parser) @@ -591,35 +413,17 @@ describe('Plugin', () => { ] const result = await chain.invoke(messages) - expect(result).to.equal('Hi!') + expect(result).to.exist await checkTraces }) it('instruments a complex langchain chain', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Why did the chicken cross the road? To get to the other side!' - } - }] - } - }) - const prompt = langchainPrompts.ChatPromptTemplate.fromTemplate( 'Tell me a short joke about {topic} in the style of {style}' ) - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const parser = new langchainOutputParsers.StringOutputParser() @@ -647,51 +451,17 @@ describe('Plugin', () => { const result = await chain.invoke({ topic: 'chickens', style: 'dad joke' }) - expect(result).to.equal('Why did the chicken cross the road? To get to the other side!') + expect(result).to.exist await checkTraces }) it('instruments a batched call', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: [ - { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Why did the chicken cross the road? To get to the other side!' - } - }] - }, - { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Why was the dog confused? It was barking up the wrong tree!' - } - }] - } - ] - }) - const prompt = langchainPrompts.ChatPromptTemplate.fromTemplate( 'Tell me a joke about {topic}' ) const parser = new langchainOutputParsers.StringOutputParser() - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const chain = langchainRunnables.RunnableSequence.from([ { @@ -715,8 +485,8 @@ describe('Plugin', () => { const result = await chain.batch(['chickens', 'dogs']) expect(result).to.have.length(2) - expect(result[0]).to.equal('Why did the chicken cross the road? To get to the other side!') - expect(result[1]).to.equal('Why was the dog confused? It was barking up the wrong tree!') + expect(result[0]).to.exist + expect(result[1]).to.exist await checkTraces }) @@ -724,19 +494,6 @@ describe('Plugin', () => { it('instruments a chain with a JSON output parser and tags it correctly', async function () { if (!langchainOutputParsers.JsonOutputParser) this.skip() - stubCall({ - ...openAiBaseChatInfo, - response: { - choices: [{ - message: { - role: 'assistant', - content: '{\n "name": "John",\n "age": 30\n}', - refusal: null - } - }] - } - }) - const checkTraces = agent .assertSomeTraces(traces => { const spans = traces[0] @@ -748,15 +505,12 @@ describe('Plugin', () => { }) const parser = new langchainOutputParsers.JsonOutputParser() - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-3.5-turbo' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-3.5-turbo' }) const chain = model.pipe(parser) const response = await chain.invoke('Generate a JSON object with name and age.') - expect(response).to.deep.equal({ - name: 'John', - age: 30 - }) + expect(response).to.exist.and.be.an('object') await checkTraces }) @@ -765,8 +519,6 @@ describe('Plugin', () => { describe('embeddings', () => { describe('@langchain/openai', () => { it('does not tag output on error', async () => { - nock('https://api.openai.com').post('/v1/embeddings').reply(403) - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -781,7 +533,8 @@ describe('Plugin', () => { }) try { - const embeddings = new langchainOpenai.OpenAIEmbeddings() + // use a bad model + const embeddings = getLangChainOpenAiClient('embedding', { model: 'gpt-3.5-turbo-instruct' }) await embeddings.embedQuery('Hello, world!') } catch {} @@ -789,26 +542,7 @@ describe('Plugin', () => { }) it('instruments a langchain openai embedQuery call', async () => { - if (semver.satisfies(langchainOpenaiOpenAiVersion, '>=4.91.0')) { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: require('./fixtures/single-embedding.json') - }) - } else { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: { - object: 'list', - data: [{ - object: 'embedding', - index: 0, - embedding: Array(1536).fill(0) - }] - } - }) - } - - const embeddings = new langchainOpenai.OpenAIEmbeddings() + const embeddings = getLangChainOpenAiClient('embedding') const checkTraces = agent .assertSomeTraces(traces => { @@ -832,29 +566,6 @@ describe('Plugin', () => { }) it('instruments a langchain openai embedDocuments call', async () => { - if (semver.satisfies(langchainOpenaiOpenAiVersion, '>=4.91.0')) { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: require('./fixtures/double-embedding.json') - }) - } else { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: { - object: 'list', - data: [{ - object: 'embedding', - index: 0, - embedding: Array(1536).fill(0) - }, { - object: 'embedding', - index: 1, - embedding: Array(1536).fill(0) - }] - } - }) - } - const checkTraces = agent .assertSomeTraces(traces => { expect(traces[0].length).to.equal(1) @@ -865,7 +576,7 @@ describe('Plugin', () => { expect(span.meta).to.have.property('langchain.request.model', 'text-embedding-ada-002') }) - const embeddings = new langchainOpenai.OpenAIEmbeddings() + const embeddings = getLangChainOpenAiClient('embedding') const documents = ['Hello, world!', 'Goodbye, world!'] const result = await embeddings.embedDocuments(documents) @@ -879,37 +590,10 @@ describe('Plugin', () => { }) describe('@langchain/google-genai', () => { - let response - let originalFetch - - beforeEach(() => { - // we don't have a good way to `nock` the requests - // they utilize `fetch`, so we'll temporarily patch it instead - originalFetch = global.fetch - global.fetch = async function () { - return Promise.resolve(response) - } - }) - - afterEach(() => { - global.fetch = originalFetch - }) - - // version compatibility issues on lower versions it('instruments a langchain google-genai embedQuery call', async function () { if (!langchainGoogleGenAI) this.skip() - response = { - json () { - return { - embedding: { - values: [-0.0034387498, -0.026400521] - } - } - }, - ok: true - } - const embeddings = new langchainGoogleGenAI.GoogleGenerativeAIEmbeddings({ + const embeddings = getLangChainGoogleGenAIClient('embedding', { model: 'text-embedding-004', taskType: 'RETRIEVAL_DOCUMENT', title: 'Document title' @@ -930,8 +614,7 @@ describe('Plugin', () => { const query = 'Hello, world!' const result = await embeddings.embedQuery(query) - expect(result).to.have.length(2) - expect(result).to.deep.equal([-0.0034387498, -0.026400521]) + expect(result).to.have.length(768) await checkTraces }) @@ -997,10 +680,7 @@ describe('Plugin', () => { let vectorstore beforeEach(async () => { - // need to mock out adding a document to the vectorstore - stubSingleEmbedding(langchainOpenaiOpenAiVersion) - - const embeddings = new langchainOpenai.OpenAIEmbeddings() + const embeddings = getLangChainOpenAiClient('embedding') vectorstore = new MemoryVectorStore(embeddings) const document = { @@ -1013,8 +693,6 @@ describe('Plugin', () => { }) it('traces a vectorstore similaritySearch call', async () => { - stubSingleEmbedding(langchainOpenaiOpenAiVersion) - const checkTraces = agent.assertSomeTraces(traces => { const spans = traces[0] @@ -1038,8 +716,6 @@ describe('Plugin', () => { }) it('traces a vectorstore similaritySearchWithScore call', async () => { - stubSingleEmbedding(langchainOpenaiOpenAiVersion) - const checkTraces = agent.assertSomeTraces(traces => { const spans = traces[0] diff --git a/packages/datadog-plugin-langchain/test/integration-test/client.spec.js b/packages/datadog-plugin-langchain/test/integration-test/client.spec.js index 1e8b72171a5..46cdcd89096 100644 --- a/packages/datadog-plugin-langchain/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-langchain/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-langchain/test/integration-test/server.mjs b/packages/datadog-plugin-langchain/test/integration-test/server.mjs index a0ce6b49f1b..edae7bd7c69 100644 --- a/packages/datadog-plugin-langchain/test/integration-test/server.mjs +++ b/packages/datadog-plugin-langchain/test/integration-test/server.mjs @@ -1,22 +1,12 @@ import { OpenAI } from '@langchain/openai' import { StringOutputParser } from '@langchain/core/output_parsers' -import nock from 'nock' - -nock('https://api.openai.com:443') - .post('/v1/completions') - .reply(200, { - model: 'gpt-3.5-turbo-instruct', - choices: [{ - text: 'The answer is 4', - index: 0, - logprobs: null, - finish_reason: 'length' - }], - usage: { prompt_tokens: 8, completion_tokens: 12, otal_tokens: 20 } - }) const llm = new OpenAI({ - apiKey: '' + apiKey: '', + configuration: { + baseURL: 'http://127.0.0.1:9126/vcr/openai' + }, + model: 'gpt-3.5-turbo-instruct' }) const parser = new StringOutputParser() diff --git a/packages/datadog-plugin-limitd-client/test/index.spec.js b/packages/datadog-plugin-limitd-client/test/index.spec.js index c1852d2296e..bf8ad54cb32 100644 --- a/packages/datadog-plugin-limitd-client/test/index.spec.js +++ b/packages/datadog-plugin-limitd-client/test/index.spec.js @@ -2,6 +2,7 @@ const { storage } = require('../../datadog-core') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Plugin', () => { let LimitdClient diff --git a/packages/datadog-plugin-limitd-client/test/integration-test/client.spec.js b/packages/datadog-plugin-limitd-client/test/integration-test/client.spec.js index 5ed8cd12c9d..9f82020cc4f 100644 --- a/packages/datadog-plugin-limitd-client/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-limitd-client/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-mariadb/test/integration-test/client.spec.js b/packages/datadog-plugin-mariadb/test/integration-test/client.spec.js index 8ff3272fba9..c8857d64ee9 100644 --- a/packages/datadog-plugin-mariadb/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-mariadb/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-memcached/test/integration-test/client.spec.js b/packages/datadog-plugin-memcached/test/integration-test/client.spec.js index 78012aa8b3a..3ce696b5947 100644 --- a/packages/datadog-plugin-memcached/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-memcached/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-microgateway-core/test/index.spec.js b/packages/datadog-plugin-microgateway-core/test/index.spec.js index 449690f8b9b..f0f3ecd0701 100644 --- a/packages/datadog-plugin-microgateway-core/test/index.spec.js +++ b/packages/datadog-plugin-microgateway-core/test/index.spec.js @@ -6,6 +6,7 @@ const os = require('os') const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') const proxy = require('./proxy') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') describe('Plugin', () => { diff --git a/packages/datadog-plugin-microgateway-core/test/integration-test/client.spec.js b/packages/datadog-plugin-microgateway-core/test/integration-test/client.spec.js index 840c5646742..15a274a1ab0 100644 --- a/packages/datadog-plugin-microgateway-core/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-microgateway-core/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-mocha/test/index.spec.js b/packages/datadog-plugin-mocha/test/index.spec.js index ba903aba9f9..3d8f9d4b724 100644 --- a/packages/datadog-plugin-mocha/test/index.spec.js +++ b/packages/datadog-plugin-mocha/test/index.spec.js @@ -7,6 +7,7 @@ const nock = require('nock') const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { ORIGIN_KEY, COMPONENT, ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const { TEST_FRAMEWORK, diff --git a/packages/datadog-plugin-moleculer/test/integration-test/client.spec.js b/packages/datadog-plugin-moleculer/test/integration-test/client.spec.js index 2d59d81eba3..127aa7fd988 100644 --- a/packages/datadog-plugin-moleculer/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-moleculer/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-mongodb-core/test/integration-test/client.spec.js b/packages/datadog-plugin-mongodb-core/test/integration-test/client.spec.js index 1013476a33f..2acdeed097a 100644 --- a/packages/datadog-plugin-mongodb-core/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-mongodb-core/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-mongoose/test/integration-test/client.spec.js b/packages/datadog-plugin-mongoose/test/integration-test/client.spec.js index 3627dd079d0..ce5fb9f67c6 100644 --- a/packages/datadog-plugin-mongoose/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-mongoose/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-mysql/test/integration-test/client.spec.js b/packages/datadog-plugin-mysql/test/integration-test/client.spec.js index 36e2c445458..a5716e1b7a6 100644 --- a/packages/datadog-plugin-mysql/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-mysql/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-mysql2/test/integration-test/client.spec.js b/packages/datadog-plugin-mysql2/test/integration-test/client.spec.js index 8e7a148d7e1..c21aa72b5c9 100644 --- a/packages/datadog-plugin-mysql2/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-mysql2/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-next/test/integration-test/client.spec.js b/packages/datadog-plugin-next/test/integration-test/client.spec.js index 1265e91b1f5..e249ff7d068 100644 --- a/packages/datadog-plugin-next/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-next/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') const hookFile = 'dd-trace/loader-hook.mjs' diff --git a/packages/datadog-plugin-openai/test/index.spec.js b/packages/datadog-plugin-openai/test/index.spec.js index 3e6c1e93883..c5baa4dcc1d 100644 --- a/packages/datadog-plugin-openai/test/index.spec.js +++ b/packages/datadog-plugin-openai/test/index.spec.js @@ -12,6 +12,7 @@ const { DogStatsDClient } = require('../../dd-trace/src/dogstatsd') const { NoopExternalLogger } = require('../../dd-trace/src/external-logger/src') const Sampler = require('../../dd-trace/src/sampler') const { useEnv } = require('../../../integration-tests/helpers') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const tracerRequirePath = '../../dd-trace' diff --git a/packages/datadog-plugin-openai/test/integration-test/client.spec.js b/packages/datadog-plugin-openai/test/integration-test/client.spec.js index eca9ec9da27..2144c78eade 100644 --- a/packages/datadog-plugin-openai/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-openai/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-opensearch/test/integration-test/client.spec.js b/packages/datadog-plugin-opensearch/test/integration-test/client.spec.js index 7121c2acf72..34badfcfb93 100644 --- a/packages/datadog-plugin-opensearch/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-opensearch/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-oracledb/test/integration-test/client.spec.js b/packages/datadog-plugin-oracledb/test/integration-test/client.spec.js index f486e69e1cd..551b40fc92a 100644 --- a/packages/datadog-plugin-oracledb/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-oracledb/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-pg/src/index.js b/packages/datadog-plugin-pg/src/index.js index 7b53df04003..76003ce00dc 100644 --- a/packages/datadog-plugin-pg/src/index.js +++ b/packages/datadog-plugin-pg/src/index.js @@ -8,7 +8,8 @@ class PGPlugin extends DatabasePlugin { static operation = 'query' static system = 'postgres' - start ({ params = {}, query, processId, stream }) { + bindStart (ctx) { + const { params = {}, query, processId, stream } = ctx const service = this.serviceName({ pluginConfig: this.config, params }) const originalStatement = this.maybeTruncate(query.text) @@ -25,13 +26,15 @@ class PGPlugin extends DatabasePlugin { 'out.host': params.host, [CLIENT_PORT_KEY]: params.port } - }) + }, ctx) if (stream) { span.setTag('db.stream', 1) } query.__ddInjectableQuery = this.injectDbmQuery(span, query.text, service, !!query.name) + + return ctx.currentStore } } diff --git a/packages/datadog-plugin-pg/test/integration-test/client.spec.js b/packages/datadog-plugin-pg/test/integration-test/client.spec.js index 18e97dac50e..16a6cd2f6c1 100644 --- a/packages/datadog-plugin-pg/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-pg/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-pino/test/integration-test/client.spec.js b/packages/datadog-plugin-pino/test/integration-test/client.spec.js index 389b765eaa9..b97f0885ac8 100644 --- a/packages/datadog-plugin-pino/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-pino/test/integration-test/client.spec.js @@ -5,6 +5,7 @@ const { createSandbox, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { expect } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-protobufjs/test/index.spec.js b/packages/datadog-plugin-protobufjs/test/index.spec.js index 30e95687bac..42ba3053608 100644 --- a/packages/datadog-plugin-protobufjs/test/index.spec.js +++ b/packages/datadog-plugin-protobufjs/test/index.spec.js @@ -1,9 +1,10 @@ 'use strict' const fs = require('fs') +const path = require('path') const { expect } = require('chai') const agent = require('../../dd-trace/test/plugins/agent') -const path = require('path') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { SCHEMA_DEFINITION, SCHEMA_ID, diff --git a/packages/datadog-plugin-redis/test/integration-test/client.spec.js b/packages/datadog-plugin-redis/test/integration-test/client.spec.js index 89836ba66d8..35e400b4458 100644 --- a/packages/datadog-plugin-redis/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-redis/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-restify/test/index.spec.js b/packages/datadog-plugin-restify/test/index.spec.js index fa26a3b8ae3..cca2c018780 100644 --- a/packages/datadog-plugin-restify/test/index.spec.js +++ b/packages/datadog-plugin-restify/test/index.spec.js @@ -5,6 +5,7 @@ const axios = require('axios') const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') const { ERROR_MESSAGE } = require('../../dd-trace/src/constants') +const { withVersions } = require('../../dd-trace/test/setup/mocha') describe('Plugin', () => { let tracer diff --git a/packages/datadog-plugin-restify/test/integration-test/client.spec.js b/packages/datadog-plugin-restify/test/integration-test/client.spec.js index 674dbc9bc08..cbf2d043272 100644 --- a/packages/datadog-plugin-restify/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-restify/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-rhea/test/integration-test/client.spec.js b/packages/datadog-plugin-rhea/test/integration-test/client.spec.js index cbcae01ecba..18e7c55de5c 100644 --- a/packages/datadog-plugin-rhea/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-rhea/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-router/test/index.spec.js b/packages/datadog-plugin-router/test/index.spec.js index 892869d64e0..4165fd88588 100644 --- a/packages/datadog-plugin-router/test/index.spec.js +++ b/packages/datadog-plugin-router/test/index.spec.js @@ -7,6 +7,7 @@ const http = require('http') const { once } = require('events') const agent = require('../../dd-trace/test/plugins/agent') const web = require('../../dd-trace/src/plugins/util/web') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const sort = spans => spans.sort((a, b) => a.start.toString() >= b.start.toString() ? 1 : -1) diff --git a/packages/datadog-plugin-router/test/integration-test/client.spec.js b/packages/datadog-plugin-router/test/integration-test/client.spec.js index 3b32836e64c..4759f2ffd85 100644 --- a/packages/datadog-plugin-router/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-router/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-sharedb/test/index.spec.js b/packages/datadog-plugin-sharedb/test/index.spec.js index 5e1934687f6..a0396991aa0 100644 --- a/packages/datadog-plugin-sharedb/test/index.spec.js +++ b/packages/datadog-plugin-sharedb/test/index.spec.js @@ -1,6 +1,7 @@ 'use strict' const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') describe('Plugin', () => { diff --git a/packages/datadog-plugin-sharedb/test/integration-test/client.spec.js b/packages/datadog-plugin-sharedb/test/integration-test/client.spec.js index 9d2aa161ca8..da5915a7838 100644 --- a/packages/datadog-plugin-sharedb/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-sharedb/test/integration-test/client.spec.js @@ -6,6 +6,7 @@ const { checkSpansForServiceName, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { assert } = require('chai') describe('esm', () => { diff --git a/packages/datadog-plugin-tedious/test/integration-test/client.spec.js b/packages/datadog-plugin-tedious/test/integration-test/client.spec.js index aa32944f541..3e9fd0221b1 100644 --- a/packages/datadog-plugin-tedious/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-tedious/test/integration-test/client.spec.js @@ -8,6 +8,7 @@ const { } = require('../../../../integration-tests/helpers') const { assert } = require('chai') const version = require('../../../../version.js') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') // tedious does not support node 20 const describe = version.NODE_MAJOR >= 20 diff --git a/packages/datadog-plugin-winston/test/index.spec.js b/packages/datadog-plugin-winston/test/index.spec.js index f32b079572e..224b6c52ea7 100644 --- a/packages/datadog-plugin-winston/test/index.spec.js +++ b/packages/datadog-plugin-winston/test/index.spec.js @@ -2,6 +2,7 @@ const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') const http = require('http') const { expect } = require('chai') const proxyquire = require('proxyquire').noPreserveCache() diff --git a/packages/datadog-plugin-winston/test/integration-test/client.spec.js b/packages/datadog-plugin-winston/test/integration-test/client.spec.js index cb94108145a..496eaf890f7 100644 --- a/packages/datadog-plugin-winston/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-winston/test/integration-test/client.spec.js @@ -5,6 +5,7 @@ const { createSandbox, spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') const { expect } = require('chai') describe('esm', () => { diff --git a/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js b/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js index b8795f809c4..2f2f5434b12 100644 --- a/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +++ b/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js @@ -16,7 +16,10 @@ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer { onConfigure () { this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL')) this.addSub('datadog:mysql2:outerquery:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL')) - this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, undefined, 'POSTGRES')) + this.addSub( + 'apm:pg:query:start', + ({ originalText, query }) => this.analyze(originalText || query.text, undefined, 'POSTGRES') + ) this.addBind( 'datadog:sequelize:query:start', @@ -24,17 +27,22 @@ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer { ) this.addSub('datadog:sequelize:query:finish', () => this.returnToParentStore()) - this.addSub('datadog:pg:pool:query:start', ({ query }) => this.setStoreAndAnalyze(query.text, 'POSTGRES')) + this.addBind('datadog:pg:pool:query:start', ({ query }) => this.getStoreAndAnalyze(query.text, 'POSTGRES')) this.addSub('datadog:pg:pool:query:finish', () => this.returnToParentStore()) this.addSub('datadog:mysql:pool:query:start', ({ sql }) => this.setStoreAndAnalyze(sql, 'MYSQL')) this.addSub('datadog:mysql:pool:query:finish', () => this.returnToParentStore()) - this.addSub('datadog:knex:raw:start', ({ sql, dialect: knexDialect }) => { + this.addBind('datadog:knex:raw:start', (context) => { + const { sql, dialect: knexDialect } = context const dialect = this.normalizeKnexDialect(knexDialect) - this.setStoreAndAnalyze(sql, dialect) + const currentStore = this.getStoreAndAnalyze(sql, dialect) + context.currentStore = currentStore + return currentStore }) - this.addSub('datadog:knex:raw:finish', () => this.returnToParentStore()) + + this.addBind('datadog:knex:raw:subscribes', ({ currentStore }) => currentStore) + this.addBind('datadog:knex:raw:finish', ({ currentStore }) => currentStore?.sqlParentStore) } setStoreAndAnalyze (query, dialect) { @@ -54,8 +62,7 @@ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer { } } - returnToParentStore () { - const store = storage('legacy').getStore() + returnToParentStore (store = storage('legacy').getStore()) { if (store && store.sqlParentStore) { storage('legacy').enterWith(store.sqlParentStore) } diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index 004d2aa410c..32a27cbacf6 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -139,12 +139,12 @@ class TaintTrackingPlugin extends SourceIastPlugin { addDatabaseSubscriptions () { this.addSub( { channelName: 'datadog:sequelize:query:finish', tag: SQL_ROW_VALUE }, - ({ result }) => this._taintDatabaseResult(result, 'sequelize') + ({ result }) => this._taintDatabaseResult(result, 'sequelize', getIastContext(storage('legacy').getStore())) ) this.addSub( { channelName: 'apm:pg:query:finish', tag: SQL_ROW_VALUE }, - ({ result }) => this._taintDatabaseResult(result, 'pg') + ({ result, currentStore }) => this._taintDatabaseResult(result, 'pg', getIastContext(currentStore)) ) } @@ -263,7 +263,7 @@ class TaintTrackingPlugin extends SourceIastPlugin { this.taintUrl(req, iastContext) } - _taintDatabaseResult (result, dbOrigin, iastContext = getIastContext(storage('legacy').getStore()), name) { + _taintDatabaseResult (result, dbOrigin, iastContext, name) { if (!iastContext) return result if (this._rowsToTaint === 0) return result diff --git a/packages/dd-trace/src/appsec/recommended.json b/packages/dd-trace/src/appsec/recommended.json index 4dc987ddfab..b39992af325 100644 --- a/packages/dd-trace/src/appsec/recommended.json +++ b/packages/dd-trace/src/appsec/recommended.json @@ -1,7 +1,7 @@ { "version": "2.2", "metadata": { - "rules_version": "1.15.0" + "rules_version": "1.15.1" }, "rules": [ { @@ -5539,6 +5539,7 @@ "confidence": "0", "module": "waf" }, + "max_version": "1.24.9", "conditions": [ { "parameters": { @@ -6671,7 +6672,10 @@ { "address": "graphql.server.resolver" } - ] + ], + "options": { + "path-inspection": true + } }, "operator": "ssrf_detector" } @@ -8916,6 +8920,271 @@ "transformers": [] } ], + "rules_compat": [ + { + "id": "api-001-100", + "name": "JWT: No expiry is present", + "tags": { + "type": "jwt", + "category": "api_security", + "confidence": "0", + "module": "business-logic" + }, + "min_version": "1.25.0", + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.jwt", + "key_path": [ + "payload", + "exp" + ] + } + ] + }, + "operator": "!exists" + } + ], + "transformers": [], + "output": { + "event": false, + "keep": false, + "attributes": { + "_dd.appsec.api.jwt.no_expiry": { + "value": 1 + } + } + } + }, + { + "id": "api-001-110", + "name": "JWT: Collect algorithm used", + "tags": { + "type": "jwt", + "category": "api_security", + "confidence": "0", + "module": "business-logic" + }, + "min_version": "1.25.0", + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.jwt", + "key_path": [ + "header", + "alg" + ] + } + ] + }, + "operator": "exists" + } + ], + "transformers": [], + "output": { + "event": false, + "keep": false, + "attributes": { + "_dd.appsec.api.jwt_alg": { + "address": "server.request.jwt", + "key_path": [ + "header", + "alg" + ] + } + } + } + }, + { + "id": "api-001-120", + "name": "JWT: No audience is specified", + "tags": { + "type": "jwt", + "category": "api_security", + "confidence": "0", + "module": "business-logic" + }, + "min_version": "1.25.0", + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.jwt", + "key_path": [ + "payload", + "aud" + ] + } + ] + }, + "operator": "!exists" + } + ], + "transformers": [], + "output": { + "event": false, + "keep": false, + "attributes": { + "_dd.appsec.api.jwt.no_audience": { + "value": 1 + } + } + } + }, + { + "id": "api-001-130", + "name": "JWT: None algorithm used", + "tags": { + "type": "jwt", + "category": "api_security", + "confidence": "0", + "module": "business-logic" + }, + "min_version": "1.25.0", + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.jwt", + "key_path": [ + "header", + "alg" + ] + } + ], + "list": [ + "none", + "nonE", + "noNe", + "noNE", + "nOne", + "nOnE", + "nONe", + "nONE", + "None", + "NonE", + "NoNe", + "NoNE", + "NOne", + "NOnE", + "NONe", + "NONE" + ] + }, + "operator": "exact_match" + } + ], + "transformers": [], + "output": { + "event": false, + "keep": true, + "attributes": { + "_dd.appsec.api.jwt.none_alg": { + "value": 1 + } + } + } + }, + { + "id": "ua0-600-551", + "name": "Datadog test scanner - scalar trace-tagging version: user-agent", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Datadog Canary Test", + "confidence": "1", + "module": "waf" + }, + "min_version": "1.25.0", + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "grpc.server.request.metadata", + "key_path": [ + "dd-canary" + ] + } + ], + "regex": "^dd-test-scanner-tag-scalar(?:$|/|\\s)" + }, + "operator": "match_regex" + } + ], + "transformers": [], + "output": { + "event": false, + "attributes": { + "_dd.appsec.test.scanner.scalar": { + "value": 1 + } + } + } + }, + { + "id": "ua0-600-552", + "name": "Datadog test scanner - reference trace-tagging version: user-agent", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Datadog Canary Test", + "confidence": "1", + "module": "waf" + }, + "min_version": "1.25.0", + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "grpc.server.request.metadata", + "key_path": [ + "dd-canary" + ] + } + ], + "regex": "^dd-test-scanner-tag-ref(?:$|/|\\s)" + }, + "operator": "match_regex" + } + ], + "transformers": [], + "output": { + "event": false, + "attributes": { + "_dd.appsec.test.scanner.reference": { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + } + } + } + ], "processors": [ { "id": "http-endpoint-fingerprint", diff --git a/packages/dd-trace/src/guardrails/telemetry.js b/packages/dd-trace/src/guardrails/telemetry.js index 3930c9c15a9..dc4a2eb4d8a 100644 --- a/packages/dd-trace/src/guardrails/telemetry.js +++ b/packages/dd-trace/src/guardrails/telemetry.js @@ -22,7 +22,10 @@ var metadata = { runtime_name: 'nodejs', runtime_version: process.versions.node, tracer_version: tracerVersion, - pid: process.pid + pid: process.pid, + result: 'unknown', + result_reason: 'unknown', + result_class: 'unknown' } var seen = {} @@ -64,14 +67,27 @@ function sendTelemetry (name, tags) { }) proc.on('error', function () { log.error('Failed to spawn telemetry forwarder') + metadata.result = 'error' + metadata.result_class = 'internal_error' + metadata.result_reason = 'Failed to spawn telemetry forwarder' }) proc.on('exit', function (code) { - if (code !== 0) { + if (code === 0) { + metadata.result = 'success' + metadata.result_class = 'success' + metadata.result_reason = 'Successfully configured ddtrace package' + } else { log.error('Telemetry forwarder exited with code', code) + metadata.result = 'error' + metadata.result_class = 'internal_error' + metadata.result_reason = 'Telemetry forwarder exited with code ' + code } }) proc.stdin.on('error', function () { log.error('Failed to write telemetry data to telemetry forwarder') + metadata.result = 'error' + metadata.result_class = 'internal_error' + metadata.result_reason = 'Failed to write telemetry data to telemetry forwarder' }) proc.stdin.end(JSON.stringify({ metadata: metadata, points: points })) } diff --git a/packages/dd-trace/src/llmobs/plugins/ai/index.js b/packages/dd-trace/src/llmobs/plugins/ai/index.js new file mode 100644 index 00000000000..e206de7febe --- /dev/null +++ b/packages/dd-trace/src/llmobs/plugins/ai/index.js @@ -0,0 +1,351 @@ +'use strict' + +const BaseLLMObsPlugin = require('../base') +const { getModelProvider } = require('../../../../../datadog-plugin-ai/src/utils') + +const { channel } = require('dc-polyfill') + +const toolCreationCh = channel('dd-trace:vercel-ai:tool') +const setAttributesCh = channel('dd-trace:vercel-ai:span:setAttributes') + +const { MODEL_NAME, MODEL_PROVIDER, NAME } = require('../../constants/tags') +const { + getSpanTags, + getOperation, + getUsage, + getJsonStringValue, + getModelMetadata, + getGenerationMetadata, + getToolNameFromTags, + getToolCallResultContent +} = require('./util') + +const SPAN_NAME_TO_KIND_MAPPING = { + // embeddings + embed: 'workflow', + embedMany: 'workflow', + doEmbed: 'embedding', + // object generation + generateObject: 'workflow', + streamObject: 'workflow', + // text generation + generateText: 'workflow', + streamText: 'workflow', + // llm operations + doGenerate: 'llm', + doStream: 'llm', + // tools + toolCall: 'tool' +} + +class VercelAILLMObsPlugin extends BaseLLMObsPlugin { + static id = 'ai' + static integration = 'ai' + static prefix = 'tracing:dd-trace:vercel-ai' + + /** + * The available tools within the runtime scope of this integration. + * This essentially acts as a global registry for all tools made through the Vercel AI SDK. + * @type {Set>} + */ + #availableTools + + /** + * A mapping of tool call IDs to tool names. + * This is used to map the tool call ID to the tool name for the output message. + * @type {Record} + */ + #toolCallIdsToName + + constructor (...args) { + super(...args) + + this.#toolCallIdsToName = {} + this.#availableTools = new Set() + toolCreationCh.subscribe(toolArgs => { + this.#availableTools.add(toolArgs) + }) + + setAttributesCh.subscribe(({ ctx, attributes }) => { + Object.assign(ctx.attributes, attributes) + }) + } + + /** + * Does a best-effort attempt to find the right tool name for the given tool description. + * This is because the Vercel AI SDK does not tag tools by name properly, but + * rather by the index they were passed in. Tool names appear nowhere in the span tags. + * + * We use the tool description as the next best identifier for a tool. + * + * @param {string} toolDescription + * @returns {string} + */ + findToolName (toolDescription) { + for (const availableTool of this.#availableTools) { + const description = availableTool.description + if (description === toolDescription) { + return availableTool.id + } + } + } + + getLLMObsSpanRegisterOptions (ctx) { + const span = ctx.currentStore?.span + const operation = getOperation(span) + const kind = SPAN_NAME_TO_KIND_MAPPING[operation] + if (!kind) return + + return { kind, name: operation } + } + + setLLMObsTags (ctx) { + const span = ctx.currentStore?.span + if (!span) return + + const operation = getOperation(span) + const kind = SPAN_NAME_TO_KIND_MAPPING[operation] + if (!kind) return + + const tags = getSpanTags(ctx) + + if (['embedding', 'llm'].includes(kind)) { + this._tagger._setTag(span, MODEL_NAME, tags['ai.model.id']) + this._tagger._setTag(span, MODEL_PROVIDER, getModelProvider(tags)) + } + + switch (operation) { + case 'embed': + case 'embedMany': + this.setEmbeddingWorkflowTags(span, tags) + break + case 'doEmbed': + this.setEmbeddingTags(span, tags) + break + case 'generateObject': + case 'streamObject': + this.setObjectGenerationTags(span, tags) + break + case 'generateText': + case 'streamText': + this.setTextGenerationTags(span, tags) + break + case 'doGenerate': + case 'doStream': + this.setLLMOperationTags(span, tags) + break + case 'toolCall': + this.setToolTags(span, tags) + break + default: + break + } + } + + setEmbeddingWorkflowTags (span, tags) { + const inputs = tags['ai.value'] ?? tags['ai.values'] + const parsedInputs = Array.isArray(inputs) + ? inputs.map(input => getJsonStringValue(input, '')) + : getJsonStringValue(inputs, '') + + const embeddingsOutput = tags['ai.embedding'] ?? tags['ai.embeddings'] + const isSingleEmbedding = !Array.isArray(embeddingsOutput) + const numberOfEmbeddings = isSingleEmbedding ? 1 : embeddingsOutput.length + const embeddingsLength = getJsonStringValue(isSingleEmbedding ? embeddingsOutput : embeddingsOutput?.[0], []).length + const output = `[${numberOfEmbeddings} embedding(s) returned with size ${embeddingsLength}]` + + this._tagger.tagTextIO(span, parsedInputs, output) + + const metadata = getGenerationMetadata(tags) + this._tagger.tagMetadata(span, metadata) + } + + setEmbeddingTags (span, tags) { + const inputs = tags['ai.values'] + if (!Array.isArray(inputs)) return + + const parsedInputs = inputs.map(input => getJsonStringValue(input, '')) + + const embeddingsOutput = tags['ai.embeddings'] + const numberOfEmbeddings = embeddingsOutput?.length + const embeddingsLength = getJsonStringValue(embeddingsOutput?.[0], []).length + const output = `[${numberOfEmbeddings} embedding(s) returned with size ${embeddingsLength}]` + + this._tagger.tagEmbeddingIO(span, parsedInputs, output) + + const usage = tags['ai.usage.tokens'] + this._tagger.tagMetrics(span, { + inputTokens: usage, + totalTokens: usage + }) + } + + setObjectGenerationTags (span, tags) { + const promptInfo = getJsonStringValue(tags['ai.prompt'], {}) + const lastUserPrompt = + promptInfo.prompt ?? + promptInfo.messages.reverse().find(message => message.role === 'user')?.content + const prompt = Array.isArray(lastUserPrompt) ? lastUserPrompt.map(part => part.text ?? '').join('') : lastUserPrompt + + const output = tags['ai.response.object'] + + this._tagger.tagTextIO(span, prompt, output) + + const metadata = getGenerationMetadata(tags) ?? {} + metadata.schema = getJsonStringValue(tags['ai.schema'], {}) + this._tagger.tagMetadata(span, metadata) + } + + setTextGenerationTags (span, tags) { + const promptInfo = getJsonStringValue(tags['ai.prompt'], {}) + const lastUserPrompt = + promptInfo.prompt ?? + promptInfo.messages.reverse().find(message => message.role === 'user')?.content + const prompt = Array.isArray(lastUserPrompt) ? lastUserPrompt.map(part => part.text ?? '').join('') : lastUserPrompt + + const output = tags['ai.response.text'] + + this._tagger.tagTextIO(span, prompt, output) + + const metadata = getGenerationMetadata(tags) + this._tagger.tagMetadata(span, metadata) + } + + setLLMOperationTags (span, tags) { + const toolsForModel = tags['ai.prompt.tools']?.map(getJsonStringValue) + + const inputMessages = getJsonStringValue(tags['ai.prompt.messages'], []) + const parsedInputMessages = [] + for (const message of inputMessages) { + const formattedMessages = this.formatMessage(message, toolsForModel) + parsedInputMessages.push(...formattedMessages) + } + + const outputMessage = this.formatOutputMessage(tags, toolsForModel) + + this._tagger.tagLLMIO(span, parsedInputMessages, outputMessage) + + const metadata = getModelMetadata(tags) + this._tagger.tagMetadata(span, metadata) + + const usage = getUsage(tags) + this._tagger.tagMetrics(span, usage) + } + + setToolTags (span, tags) { + const toolCallId = tags['ai.toolCall.id'] + const name = getToolNameFromTags(tags) ?? this.#toolCallIdsToName[toolCallId] + if (name) this._tagger._setTag(span, NAME, name) + + const input = tags['ai.toolCall.args'] + const output = tags['ai.toolCall.result'] + + this._tagger.tagTextIO(span, input, output) + } + + formatOutputMessage (tags, toolsForModel) { + const outputMessageText = tags['ai.response.text'] ?? tags['ai.response.object'] + const outputMessageToolCalls = getJsonStringValue(tags['ai.response.toolCalls'], []) + + const formattedToolCalls = [] + for (const toolCall of outputMessageToolCalls) { + const toolCallArgs = getJsonStringValue(toolCall.args, {}) + const toolDescription = toolsForModel?.find(tool => toolCall.toolName === tool.name)?.description + const name = this.findToolName(toolDescription) + this.#toolCallIdsToName[toolCall.toolCallId] = name + + formattedToolCalls.push({ + arguments: toolCallArgs, + name, + toolId: toolCall.toolCallId, + type: 'function' + }) + } + + return { + role: 'assistant', + content: outputMessageText, + toolCalls: formattedToolCalls + } + } + + /** + * Returns a list of formatted messages from a message object. + * Most of these will just be one entry, but in the case of a "tool" role, + * it is possible to have multiple tool call results in a single message that we + * need to split into multiple messages. + * + * @param {*} message + * @param {*} toolsForModel + * @returns {Array<{role: string, content: string, toolId?: string, + * toolCalls?: Array<{arguments: string, name: string, toolId: string, type: string}>}>} + */ + formatMessage (message, toolsForModel) { + const { role, content } = message + + if (role === 'system') { + return [{ role, content }] + } else if (role === 'user') { + let finalContent = '' + for (const part of content) { + const { type } = part + if (type === 'text') { + finalContent += part.text + } + } + + return [{ role, content: finalContent }] + } else if (role === 'assistant') { + const toolCalls = [] + let finalContent = '' + + for (const part of content) { + const { type } = part + // TODO(sabrenner): do we want to include reasoning? + if (['text', 'reasoning', 'redacted-reasoning'].includes(type)) { + finalContent += part.text ?? part.data + } else if (type === 'tool-call') { + const toolDescription = toolsForModel?.find(tool => part.toolName === tool.name)?.description + const name = this.findToolName(toolDescription) + + toolCalls.push({ + arguments: part.args, + name, + toolId: part.toolCallId, + type: 'function' + }) + } + } + + const finalMessage = { + role, + content: finalContent + } + + if (toolCalls.length) { + finalMessage.toolCalls = toolCalls.length ? toolCalls : undefined + } + + return [finalMessage] + } else if (role === 'tool') { + const finalMessages = [] + for (const part of content) { + if (part.type === 'tool-result') { + const safeResult = getToolCallResultContent(part) + + finalMessages.push({ + role, + content: safeResult, + toolId: part.toolCallId + }) + } + } + + return finalMessages + } + + return [] + } +} + +module.exports = VercelAILLMObsPlugin diff --git a/packages/dd-trace/src/llmobs/plugins/ai/util.js b/packages/dd-trace/src/llmobs/plugins/ai/util.js new file mode 100644 index 00000000000..635bd0b3ff7 --- /dev/null +++ b/packages/dd-trace/src/llmobs/plugins/ai/util.js @@ -0,0 +1,179 @@ +'use strict' + +const MODEL_METADATA_KEYS = new Set([ + 'frequency_penalty', + 'max_tokens', + 'presence_penalty', + 'temperature', + 'top_p', + 'top_k', + 'stop_sequences' +]) + +/** + * Get the span tags from the context (either the attributes or the span tags). + * + * @param {Record} ctx + * @returns {Record} + */ +function getSpanTags (ctx) { + const span = ctx.currentStore?.span + const carrier = ctx.attributes ?? span?.context()._tags ?? {} + return carrier +} + +/** + * Get the operation name from the span name + * + * @example + * span._name = 'ai.generateText' + * getOperation(span) // 'generateText' + * + * @example + * span._name = 'ai.generateText.doGenerate' + * getOperation(span) // 'doGenerate' + * + * @param {import('../../../opentracing/span')} span + * @returns {string} + */ +function getOperation (span) { + const name = span._name + if (!name) return + + return name.split('.').pop() +} + +/** + * Get the LLM token usage from the span tags + * @param {Record} tags + * @returns {{inputTokens: number, outputTokens: number, totalTokens: number}} + */ +function getUsage (tags) { + const usage = {} + const inputTokens = tags['ai.usage.promptTokens'] + const outputTokens = tags['ai.usage.completionTokens'] + + if (inputTokens != null) usage.inputTokens = inputTokens + if (outputTokens != null) usage.outputTokens = outputTokens + + const totalTokens = inputTokens + outputTokens + if (!Number.isNaN(totalTokens)) usage.totalTokens = totalTokens + + return usage +} + +/** + * Safely JSON parses a string value with a default fallback + * @param {string} str + * @param {any} defaultValue + * @returns {Record | string | Array} + */ +function getJsonStringValue (str, defaultValue) { + let maybeValue = defaultValue + try { + maybeValue = JSON.parse(str) + } catch { + // do nothing + } + + return maybeValue +} + +/** + * Get the model metadata from the span tags (top_p, top_k, temperature, etc.) + * @param {import('../../../opentracing/span')} span + * @returns {Record | null} + */ +function getModelMetadata (tags) { + const modelMetadata = {} + for (const metadata of MODEL_METADATA_KEYS) { + const metadataTagKey = `gen_ai.request.${metadata}` + const metadataValue = tags[metadataTagKey] + if (metadataValue) { + modelMetadata[metadata] = metadataValue + } + } + + return Object.keys(modelMetadata).length ? modelMetadata : null +} + +/** + * Get the generation metadata from the span tags (maxSteps, maxRetries, etc.) + * @param {Record} tags + * @returns {Record | null} + */ +function getGenerationMetadata (tags) { + const metadata = {} + + for (const tag of Object.keys(tags)) { + if (!tag.startsWith('ai.settings')) continue + + const settingKey = tag.split('.').pop() + const transformedKey = settingKey.replaceAll(/[A-Z]/g, letter => '_' + letter.toLowerCase()) + if (MODEL_METADATA_KEYS.has(transformedKey)) continue + + const settingValue = tags[tag] + metadata[settingKey] = settingValue + } + + return Object.keys(metadata).length ? metadata : null +} + +/** + * Get the tool name from the span tags. + * If the tool name is a parsable number, or is not found, null is returned. + * Older versions of the ai sdk would tag the tool name as its index in the tools array. + * + * @param {Record} tags + * @returns {string | null} + */ +function getToolNameFromTags (tags) { + const toolName = tags['ai.toolCall.name'] + if (!toolName) return null + + const parsedToolName = Number.parseInt(toolName) + if (!Number.isNaN(parsedToolName)) return null + + return toolName +} + +/** + * Get the content of a tool call result. + * Version 5 of the ai sdk sets this tag as `content.output`, with a ` + * @param {Record} content + * @returns {string} + */ +function getToolCallResultContent (content) { + const { output, result } = content + if (output) { + if (output.type === 'text') { + return output.value + } else if (output.type === 'json') { + return JSON.stringify(output.value) + } + return '[Unparsable Tool Result]' + } else if (result) { + if (typeof result === 'string') { + return result + } + + try { + return JSON.stringify(result) + } catch { + return '[Unparsable Tool Result]' + } + } else { + return '[Unsupported Tool Result]' + } +} + +module.exports = { + getSpanTags, + getOperation, + getUsage, + getJsonStringValue, + getModelMetadata, + getGenerationMetadata, + getToolNameFromTags, + getToolCallResultContent +} diff --git a/packages/dd-trace/src/llmobs/writers/base.js b/packages/dd-trace/src/llmobs/writers/base.js index 558be052b67..8895bd81130 100644 --- a/packages/dd-trace/src/llmobs/writers/base.js +++ b/packages/dd-trace/src/llmobs/writers/base.js @@ -1,6 +1,7 @@ 'use strict' const request = require('../../exporters/common/request') +const { getEnvironmentVariable } = require('../../config-helper') const { URL, format } = require('node:url') const path = require('node:path') @@ -17,8 +18,8 @@ const { parseResponseAndLog } = require('./util') class BaseLLMObsWriter { constructor ({ interval, timeout, eventType, config, endpoint, intake }) { - this._interval = interval || 1000 // 1s - this._timeout = timeout || 5000 // 5s + this._interval = interval ?? getEnvironmentVariable('_DD_LLMOBS_FLUSH_INTERVAL') ?? 1000 // 1s + this._timeout = timeout ?? getEnvironmentVariable('_DD_LLMOBS_TIMEOUT') ?? 5000 // 5s this._eventType = eventType this._buffer = [] diff --git a/packages/dd-trace/src/opentracing/span_context.js b/packages/dd-trace/src/opentracing/span_context.js index 1cdfeea1ae8..b6d37164dbe 100644 --- a/packages/dd-trace/src/opentracing/span_context.js +++ b/packages/dd-trace/src/opentracing/span_context.js @@ -59,6 +59,10 @@ class DatadogSpanContext { return this._spanId.toString(10) } + toBigIntSpanId () { + return this._spanId.toBigInt() + } + toTraceparent () { const flags = this._sampling.priority >= AUTO_KEEP ? '01' : '00' const traceId = this.toTraceId(true) diff --git a/packages/dd-trace/src/plugin_manager.js b/packages/dd-trace/src/plugin_manager.js index ed4639d5cdc..8305fcf89bc 100644 --- a/packages/dd-trace/src/plugin_manager.js +++ b/packages/dd-trace/src/plugin_manager.js @@ -1,7 +1,7 @@ 'use strict' const { channel } = require('dc-polyfill') -const { isFalse, normalizePluginEnvName } = require('./util') +const { isFalse, isTrue, normalizePluginEnvName } = require('./util') const plugins = require('./plugins') const log = require('./log') const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') @@ -32,8 +32,7 @@ loadChannel.subscribe(({ name }) => { function maybeEnable (Plugin) { if (!Plugin || typeof Plugin !== 'function') return if (!pluginClasses[Plugin.id]) { - const envName = `DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED` - const enabled = getEnvironmentVariable(normalizePluginEnvName(envName)) + const enabled = getEnvEnabled(Plugin) // TODO: remove the need to load the plugin class in order to disable the plugin if (isFalse(enabled) || disabledPlugins.has(Plugin.id)) { @@ -46,6 +45,11 @@ function maybeEnable (Plugin) { } } +function getEnvEnabled (Plugin) { + const envName = `DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED` + return getEnvironmentVariable(normalizePluginEnvName(envName)) +} + // TODO this must always be a singleton. module.exports = class PluginManager { constructor (tracer) { @@ -74,7 +78,7 @@ module.exports = class PluginManager { this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig) } const pluginConfig = this._configsByName[name] || { - enabled: this._tracerConfig.plugins !== false + enabled: this._tracerConfig.plugins !== false && (!Plugin.experimental || isTrue(getEnvEnabled(Plugin))) } // extracts predetermined configuration from tracer and combines it with plugin-specific config diff --git a/packages/dd-trace/src/plugins/index.js b/packages/dd-trace/src/plugins/index.js index 164fe36fde3..10e1105df04 100644 --- a/packages/dd-trace/src/plugins/index.js +++ b/packages/dd-trace/src/plugins/index.js @@ -26,6 +26,7 @@ module.exports = { get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') }, get '@vitest/runner' () { return require('../../../datadog-plugin-vitest/src') }, get aerospike () { return require('../../../datadog-plugin-aerospike/src') }, + get ai () { return require('../../../datadog-plugin-ai/src') }, get amqp10 () { return require('../../../datadog-plugin-amqp10/src') }, get amqplib () { return require('../../../datadog-plugin-amqplib/src') }, get avsc () { return require('../../../datadog-plugin-avsc/src') }, diff --git a/packages/dd-trace/src/plugins/util/ip_extractor.js b/packages/dd-trace/src/plugins/util/ip_extractor.js index 2d879475d28..dfb7dcbe5e8 100644 --- a/packages/dd-trace/src/plugins/util/ip_extractor.js +++ b/packages/dd-trace/src/plugins/util/ip_extractor.js @@ -3,11 +3,14 @@ const { BlockList } = require('net') const net = require('net') +const FORWARED_HEADER_NAME = 'forwarded' + const ipHeaderList = [ 'x-forwarded-for', 'x-real-ip', 'true-client-ip', 'x-client-ip', + FORWARED_HEADER_NAME, 'forwarded-for', 'x-cluster-client-ip', 'fastly-client-ip', @@ -49,7 +52,8 @@ function extractIp (config, req) { let firstPrivateIp if (headers) { for (const ipHeaderName of ipHeaderList) { - const firstIp = findFirstIp(headers[ipHeaderName]) + const header = headers[ipHeaderName] + const firstIp = ipHeaderName === FORWARED_HEADER_NAME ? findFirstIpForwardedFormat(header) : findFirstIp(header) if (firstIp.public) { return firstIp.public @@ -62,6 +66,10 @@ function extractIp (config, req) { return firstPrivateIp || req.socket?.remoteAddress } +function isPublicIp (ip, type) { + return !privateIPMatcher.check(ip, type === 6 ? 'ipv6' : 'ipv4') +} + function findFirstIp (str) { const result = {} if (!str) return result @@ -76,10 +84,10 @@ function findFirstIp (str) { const type = net.isIP(chunk) if (!type) continue - if (!privateIPMatcher.check(chunk, type === 6 ? 'ipv6' : 'ipv4')) { + if (isPublicIp(chunk, type)) { // it's public, return it immediately result.public = chunk - break + return result } // it's private, only save the first one found @@ -89,6 +97,39 @@ function findFirstIp (str) { return result } +const forwardedForRegexp = /for="?\[?(([0-9]+\.)+[0-9]+|[0-9a-f:]*:[0-9a-f]*)/i +const forwardedByRegexp = /by="?\[?(([0-9]+\.)+[0-9]+|[0-9a-f:]*:[0-9a-f]*)/i +const forwardedRegexps = [forwardedForRegexp, forwardedByRegexp] + +function findFirstIpForwardedFormat (str) { + const result = {} + if (!str) return result + + const splitted = str.split(',') + + for (const part of splitted) { + const chunk = part.trim() + + for (const regex of forwardedRegexps) { + const ip = regex.exec(chunk)?.[1] + + const type = net.isIP(ip) + if (!type) continue + + if (isPublicIp(ip, type)) { + // it's public, return it immediately + result.public = ip + return result + } + + // it's private, only save the first one found + if (!result.private) result.private = ip + } + } + + return result +} + module.exports = { extractIp, ipHeaderList diff --git a/packages/dd-trace/src/profiling/profilers/event_plugins/event.js b/packages/dd-trace/src/profiling/profilers/event_plugins/event.js index 4812990bc88..b3eba5b892e 100644 --- a/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +++ b/packages/dd-trace/src/profiling/profilers/event_plugins/event.js @@ -7,54 +7,55 @@ const { performance } = require('perf_hooks') // start/error/finish methods to the appropriate diagnostic channels. // TODO: Decouple this from TracingPlugin. class EventPlugin extends TracingPlugin { + #eventHandler + #eventFilter + #dataSymbol + #entryType + constructor (eventHandler, eventFilter) { super() - this.eventHandler = eventHandler - this.eventFilter = eventFilter - this.contextData = new WeakMap() - this.entryType = this.constructor.entryType + this.#eventHandler = eventHandler + this.#eventFilter = eventFilter + this.#entryType = this.constructor.entryType + this.#dataSymbol = Symbol(`dd-trace.profiling.event.${this.#entryType}.${this.constructor.operation}`) } start (ctx) { - this.contextData.set(ctx, { - startEvent: ctx, - startTime: performance.now() - }) + ctx[this.#dataSymbol] = performance.now() } error (ctx) { - const data = this.contextData.get(ctx) - if (data) { - data.error = true - } + // We don't emit perf events for failed operations + ctx[this.#dataSymbol] = undefined } finish (ctx) { - const data = this.contextData.get(ctx) - - if (!data) return + const startTime = ctx[this.#dataSymbol] + if (startTime === undefined) { + return + } + ctx[this.#dataSymbol] = undefined - const { startEvent, startTime, error } = data - if (error || this.ignoreEvent(startEvent)) { - return // don't emit perf events for failed operations or ignored events + if (this.ignoreEvent(ctx)) { + return // don't emit perf events for ignored events } const duration = performance.now() - startTime const event = { - entryType: this.entryType, + entryType: this.#entryType, startTime, duration } - if (!this.eventFilter(event)) { + if (!this.#eventFilter(event)) { return } const context = (ctx.currentStore?.span || this.activeSpan)?.context() - event._ddSpanId = context?.toSpanId() - event._ddRootSpanId = context?._trace.started[0]?.context().toSpanId() || event._ddSpanId + event._ddSpanId = context?.toBigIntSpanId() + event._ddRootSpanId = context?._trace.started[0]?.context().toBigIntSpanId() || event._ddSpanId - this.eventHandler(this.extendEvent(event, startEvent)) + this.#eventHandler(this.extendEvent(event, ctx)) } ignoreEvent () { diff --git a/packages/dd-trace/src/profiling/profilers/events.js b/packages/dd-trace/src/profiling/profilers/events.js index 872c7821db7..05328bb57de 100644 --- a/packages/dd-trace/src/profiling/profilers/events.js +++ b/packages/dd-trace/src/profiling/profilers/events.js @@ -273,10 +273,11 @@ class EventSerializer { new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) }) ] if (_ddSpanId) { - label.push(labelFromStr(this.stringTable, this.spanIdKey, _ddSpanId)) + label.push( + new Label({ key: this.spanIdKey, num: _ddSpanId })) } if (_ddRootSpanId) { - label.push(labelFromStr(this.stringTable, this.rootSpanIdKey, _ddRootSpanId)) + label.push(new Label({ key: this.rootSpanIdKey, num: _ddRootSpanId })) } const sampleInput = { diff --git a/packages/dd-trace/src/profiling/profilers/wall.js b/packages/dd-trace/src/profiling/profilers/wall.js index 32b63ea8cb2..c73d690588d 100644 --- a/packages/dd-trace/src/profiling/profilers/wall.js +++ b/packages/dd-trace/src/profiling/profilers/wall.js @@ -215,10 +215,10 @@ class NativeWallProfiler { _updateContext (context) { if (context.spanId !== null && typeof context.spanId === 'object') { - context.spanId = context.spanId.toString(10) + context.spanId = context.spanId.toBigInt() } if (context.rootSpanId !== null && typeof context.rootSpanId === 'object') { - context.rootSpanId = context.rootSpanId.toString(10) + context.rootSpanId = context.rootSpanId.toBigInt() } if (context.webTags !== undefined && context.endpoint === undefined) { // endpoint may not be determined yet, but keep it as fallback diff --git a/packages/dd-trace/src/supported-configurations.json b/packages/dd-trace/src/supported-configurations.json index 8e2f5a175c4..e9ba71e91ce 100644 --- a/packages/dd-trace/src/supported-configurations.json +++ b/packages/dd-trace/src/supported-configurations.json @@ -129,6 +129,7 @@ "DD_PROFILING_HEAP_ENABLED": ["A"], "DD_PROFILING_PROFILERS": ["A"], "DD_PROFILING_SOURCE_MAP": ["A"], + "DD_PROFILING_TIMELINE_ENABLED": ["A"], "DD_PROFILING_UPLOAD_PERIOD": ["A"], "DD_PROFILING_V8_PROFILER_BUG_WORKAROUND": ["A"], "DD_PROFILING_WALLTIME_ENABLED": ["A"], @@ -160,6 +161,7 @@ "DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED": ["A"], "DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED": ["A"], "DD_TRACE_AEROSPIKE_ENABLED": ["A"], + "DD_TRACE_AI_ENABLED": ["A"], "DD_TRACE_AGENT_PORT": ["A"], "DD_TRACE_AGENT_PROTOCOL_VERSION": ["A"], "DD_TRACE_AGENT_URL": ["A"], diff --git a/packages/dd-trace/src/tracer_metadata.js b/packages/dd-trace/src/tracer_metadata.js index 2d1575283ff..67b427ecef1 100644 --- a/packages/dd-trace/src/tracer_metadata.js +++ b/packages/dd-trace/src/tracer_metadata.js @@ -14,7 +14,7 @@ function storeConfig (config) { config.tags['runtime-id'], tracerVersion, config.hostname, - config.server || null, + config.service || null, config.env || null, config.version || null ) diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js index 7d43ca64461..fdb9b20faba 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js @@ -7,6 +7,7 @@ const path = require('path') const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') withVersions('express', 'express', expressVersion => { describe('Attacker fingerprinting', () => { diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.fastify.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.fastify.plugin.spec.js index 504d8bff0c4..f93cbc72793 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.fastify.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.fastify.plugin.spec.js @@ -7,6 +7,7 @@ const path = require('path') const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') +const { withVersions } = require('../setup/mocha') withVersions('fastify', 'fastify', fastifyVersion => { describe('Attacker fingerprinting', () => { diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js index 6291ae2d969..b8f8e25495b 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js @@ -6,6 +6,7 @@ const { assert } = require('chai') const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') +const { withVersions } = require('../setup/mocha') function assertFingerprintInTraces (traces) { const span = traces[0][0] diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js index 8285d36256b..dc384f58227 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js @@ -6,6 +6,7 @@ const { assert } = require('chai') const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') +const { withVersions } = require('../setup/mocha') function assertFingerprintInTraces (traces) { const span = traces[0][0] diff --git a/packages/dd-trace/test/appsec/graphql.apollo-server-express.plugin.spec.js b/packages/dd-trace/test/appsec/graphql.apollo-server-express.plugin.spec.js index c23490a43d0..6cc95199546 100644 --- a/packages/dd-trace/test/appsec/graphql.apollo-server-express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/graphql.apollo-server-express.plugin.spec.js @@ -6,6 +6,7 @@ const { resolvers, graphqlCommonTests } = require('./graphql.test-utils') +const { withVersions } = require('../setup/mocha') withVersions('apollo-server-core', 'express', '>=4', expressVersion => { withVersions('apollo-server-core', 'apollo-server-express', apolloServerExpressVersion => { diff --git a/packages/dd-trace/test/appsec/graphql.apollo-server-fastify.plugin.spec.js b/packages/dd-trace/test/appsec/graphql.apollo-server-fastify.plugin.spec.js index 7c64428fd6d..bfcf0eeee01 100644 --- a/packages/dd-trace/test/appsec/graphql.apollo-server-fastify.plugin.spec.js +++ b/packages/dd-trace/test/appsec/graphql.apollo-server-fastify.plugin.spec.js @@ -6,6 +6,7 @@ const { resolvers, graphqlCommonTests } = require('./graphql.test-utils') +const { withVersions } = require('../setup/mocha') withVersions('apollo-server-core', 'fastify', '3', fastifyVersion => { withVersions('apollo-server-core', 'apollo-server-fastify', apolloServerFastifyVersion => { diff --git a/packages/dd-trace/test/appsec/graphql.apollo-server.plugin.spec.js b/packages/dd-trace/test/appsec/graphql.apollo-server.plugin.spec.js index dd07ee62733..59c1e07091d 100644 --- a/packages/dd-trace/test/appsec/graphql.apollo-server.plugin.spec.js +++ b/packages/dd-trace/test/appsec/graphql.apollo-server.plugin.spec.js @@ -7,6 +7,7 @@ const { resolvers, graphqlCommonTests } = require('./graphql.test-utils') +const { withVersions } = require('../setup/mocha') withVersions('apollo-server', '@apollo/server', apolloServerVersion => { const config = {} diff --git a/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js index 49c650328df..d700042e245 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js @@ -10,6 +10,7 @@ const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking const { SQL_ROW_VALUE } = require('../../../../src/appsec/iast/taint-tracking/source-types') const { storage } = require('../../../../../datadog-core') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') +const { withVersions } = require('../../../setup/mocha') describe('Code injection vulnerability', () => { withVersions('express', 'express', version => { diff --git a/packages/dd-trace/test/appsec/iast/analyzers/command-injection-analyzer.kafkajs.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/command-injection-analyzer.kafkajs.plugin.spec.js index ecbb25ac865..240722a6fd0 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/command-injection-analyzer.kafkajs.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/command-injection-analyzer.kafkajs.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { testOutsideRequestHasVulnerability } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const topic = 'test-topic' diff --git a/packages/dd-trace/test/appsec/iast/analyzers/insecure-cookie-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/insecure-cookie-analyzer.express.plugin.spec.js index c45d3b49591..3b495e22674 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/insecure-cookie-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/insecure-cookie-analyzer.express.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIastInExpress } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const fs = require('fs') const os = require('os') const path = require('path') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/ldap-injection-analyzer.ldapjs.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/ldap-injection-analyzer.ldapjs.plugin.spec.js index dc94a5e568d..de3ea922c99 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/ldap-injection-analyzer.ldapjs.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/ldap-injection-analyzer.ldapjs.plugin.spec.js @@ -6,6 +6,7 @@ const path = require('path') const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const vulnerabilityReporter = require('../../../../src/appsec/iast/vulnerability-reporter') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/no-httponly-cookie-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/no-httponly-cookie-analyzer.express.plugin.spec.js index 5eb9cc18f39..be5f3248d4f 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/no-httponly-cookie-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/no-httponly-cookie-analyzer.express.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIastInExpress } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const fs = require('fs') const os = require('os') const path = require('path') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/no-samesite-cookie-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/no-samesite-cookie-analyzer.express.plugin.spec.js index 023596cfe00..e0e0064a7f3 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/no-samesite-cookie-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/no-samesite-cookie-analyzer.express.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIastInExpress } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const fs = require('fs') const os = require('os') const path = require('path') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js index e85106d0ae7..857f62f81d9 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js @@ -6,6 +6,7 @@ const os = require('os') const path = require('path') const { prepareTestServerForIastInExpress } = require('../utils') const agent = require('../../../plugins/agent') +const { withVersions } = require('../../../setup/mocha') describe('nosql injection detection in mongodb - whole feature', () => { // https://github.com/fiznool/express-mongo-sanitize/issues/200 diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js index 2a1ff085857..605f8edb4e5 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js @@ -3,6 +3,7 @@ const { prepareTestServerForIastInExpress } = require('../utils') const axios = require('axios') const agent = require('../../../plugins/agent') +const { withVersions } = require('../../../setup/mocha') const semver = require('semver') const os = require('os') const path = require('path') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js index 52520ed6c69..86073fe5e94 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js @@ -3,6 +3,7 @@ const { prepareTestServerForIastInExpress } = require('../utils') const axios = require('axios') const agent = require('../../../plugins/agent') +const { withVersions } = require('../../../setup/mocha') const os = require('os') const path = require('path') const fs = require('fs') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/resources/knex-sql-injection-methods.js b/packages/dd-trace/test/appsec/iast/analyzers/resources/knex-sql-injection-methods.js index 876d969280f..df7ace6af66 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/resources/knex-sql-injection-methods.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/resources/knex-sql-injection-methods.js @@ -35,10 +35,25 @@ function executeKnexNestedRawQueryAsCallback (knex, taintedSql, sqlToFail, cb) { }) } +async function executeKnexAsyncNestedRawQuery (knex, taintedSql, notTaintedSql) { + await knex.raw(notTaintedSql) + await knex.raw(taintedSql) +} + +async function executeKnexAsyncNestedRawQueryAsAsyncTryCatch (knex, taintedSql, sqlToFail) { + try { + await knex.raw(sqlToFail) + } catch (e) { + await knex.raw(taintedSql) + } +} + module.exports = { executeKnexRawQuery, executeKnexNestedRawQuery, + executeKnexAsyncNestedRawQuery, executeKnexNestedRawQueryOnRejectedInThen, executeKnexNestedRawQueryWitCatch, - executeKnexNestedRawQueryAsCallback + executeKnexNestedRawQueryAsCallback, + executeKnexAsyncNestedRawQueryAsAsyncTryCatch } diff --git a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.knex.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.knex.plugin.spec.js index 12524327a79..42f6cc3af46 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.knex.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.knex.plugin.spec.js @@ -6,6 +6,7 @@ const path = require('path') const semver = require('semver') const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const vulnerabilityReporter = require('../../../../src/appsec/iast/vulnerability-reporter') @@ -14,7 +15,7 @@ describe('sql-injection-analyzer with knex', () => { withVersions('knex', 'knex', knexVersion => { if (!semver.satisfies(knexVersion, '>=2')) return - withVersions('pg', 'pg', pgVersion => { + withVersions('pg', 'pg', () => { let knex prepareTestServerForIast('knex + pg', @@ -88,6 +89,26 @@ describe('sql-injection-analyzer with knex', () => { }) }) + describe('nested raw query - using async instead of then', () => { + testThatRequestHasVulnerability(() => { + const store = storage('legacy').getStore() + const iastCtx = iastContextFunctions.getIastContext(store) + + let taintedSql = 'SELECT 1' + taintedSql = newTaintedString(iastCtx, taintedSql, 'param', 'Request') + + const notTaintedSql = 'SELECT 1' + + return queryMethods.executeKnexAsyncNestedRawQuery(knex, taintedSql, notTaintedSql) + }, 'SQL_INJECTION', { + occurrences: 1, + location: { + path: 'knex-sql-injection-methods.js', + line: 40 + } + }) + }) + describe('nested raw query - onRejected as then argument', () => { testThatRequestHasVulnerability(() => { const store = storage('legacy').getStore() @@ -128,6 +149,26 @@ describe('sql-injection-analyzer with knex', () => { }) }) + describe('nested raw query - async try catch', () => { + testThatRequestHasVulnerability(() => { + const store = storage('legacy').getStore() + const iastCtx = iastContextFunctions.getIastContext(store) + + let taintedSql = 'SELECT 1' + taintedSql = newTaintedString(iastCtx, taintedSql, 'param', 'Request') + + const sqlToFail = 'SELECT * FROM NON_EXISTSING_TABLE' + + return queryMethods.executeKnexAsyncNestedRawQueryAsAsyncTryCatch(knex, taintedSql, sqlToFail) + }, 'SQL_INJECTION', { + occurrences: 1, + location: { + path: 'knex-sql-injection-methods.js', + line: 47 + } + }) + }) + describe('nested raw query - asCallback', () => { testThatRequestHasVulnerability(() => { return new Promise((resolve, reject) => { diff --git a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql.plugin.spec.js index 1c802b5634b..4bf8838d7d4 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql.plugin.spec.js @@ -5,6 +5,7 @@ const os = require('os') const path = require('path') const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const vulnerabilityReporter = require('../../../../src/appsec/iast/vulnerability-reporter') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql2.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql2.plugin.spec.js index b2c763f4f7f..bb1a89c55c7 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql2.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.mysql2.plugin.spec.js @@ -6,6 +6,7 @@ const fs = require('fs') const { assert } = require('chai') const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const vulnerabilityReporter = require('../../../../src/appsec/iast/vulnerability-reporter') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.pg.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.pg.plugin.spec.js index 76ce6f25c67..bc1636a1384 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.pg.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.pg.plugin.spec.js @@ -5,6 +5,7 @@ const os = require('os') const path = require('path') const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const vulnerabilityReporter = require('../../../../src/appsec/iast/vulnerability-reporter') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.sequelize.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.sequelize.plugin.spec.js index 9adfa149af6..596bd2bf1a2 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.sequelize.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.sequelize.plugin.spec.js @@ -6,6 +6,7 @@ const path = require('path') const semver = require('semver') const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const vulnerabilityReporter = require('../../../../src/appsec/iast/vulnerability-reporter') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.spec.js index 43ea2b0d773..c67a8097d48 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/sql-injection-analyzer.spec.js @@ -64,20 +64,21 @@ describe('sql-injection-analyzer', () => { sqlInjectionAnalyzer.configure(true) it('should subscribe to mysql, mysql2 and pg start query channel', () => { - expect(sqlInjectionAnalyzer._subscriptions).to.have.lengthOf(10) + expect(sqlInjectionAnalyzer._subscriptions).to.have.lengthOf(7) expect(sqlInjectionAnalyzer._subscriptions[0]._channel.name).to.equals('apm:mysql:query:start') expect(sqlInjectionAnalyzer._subscriptions[1]._channel.name).to.equals('datadog:mysql2:outerquery:start') expect(sqlInjectionAnalyzer._subscriptions[2]._channel.name).to.equals('apm:pg:query:start') expect(sqlInjectionAnalyzer._subscriptions[3]._channel.name).to.equals('datadog:sequelize:query:finish') - expect(sqlInjectionAnalyzer._subscriptions[4]._channel.name).to.equals('datadog:pg:pool:query:start') - expect(sqlInjectionAnalyzer._subscriptions[5]._channel.name).to.equals('datadog:pg:pool:query:finish') - expect(sqlInjectionAnalyzer._subscriptions[6]._channel.name).to.equals('datadog:mysql:pool:query:start') - expect(sqlInjectionAnalyzer._subscriptions[7]._channel.name).to.equals('datadog:mysql:pool:query:finish') - expect(sqlInjectionAnalyzer._subscriptions[8]._channel.name).to.equals('datadog:knex:raw:start') - expect(sqlInjectionAnalyzer._subscriptions[9]._channel.name).to.equals('datadog:knex:raw:finish') - - expect(sqlInjectionAnalyzer._bindings).to.have.lengthOf(1) + expect(sqlInjectionAnalyzer._subscriptions[4]._channel.name).to.equals('datadog:pg:pool:query:finish') + expect(sqlInjectionAnalyzer._subscriptions[5]._channel.name).to.equals('datadog:mysql:pool:query:start') + expect(sqlInjectionAnalyzer._subscriptions[6]._channel.name).to.equals('datadog:mysql:pool:query:finish') + + expect(sqlInjectionAnalyzer._bindings).to.have.lengthOf(5) expect(sqlInjectionAnalyzer._bindings[0]._channel.name).to.equals('datadog:sequelize:query:start') + expect(sqlInjectionAnalyzer._bindings[1]._channel.name).to.equals('datadog:pg:pool:query:start') + expect(sqlInjectionAnalyzer._bindings[2]._channel.name).to.equals('datadog:knex:raw:start') + expect(sqlInjectionAnalyzer._bindings[3]._channel.name).to.equals('datadog:knex:raw:subscribes') + expect(sqlInjectionAnalyzer._bindings[4]._channel.name).to.equals('datadog:knex:raw:finish') }) it('should not detect vulnerability when no query', () => { @@ -206,23 +207,23 @@ describe('sql-injection-analyzer', () => { it('should call analyze on apm:mysql:query:start', () => { const onMysqlQueryStart = sqlInjectionAnalyzer._subscriptions[0]._handler - onMysqlQueryStart({ sql: 'SELECT 1', name: 'apm:mysql:query:start' }) + onMysqlQueryStart({ sql: 'SELECT 1' }) expect(analyze).to.be.calledOnceWith('SELECT 1') }) it('should call analyze on apm:mysql2:query:start', () => { - const onMysql2QueryStart = sqlInjectionAnalyzer._subscriptions[0]._handler + const onMysql2QueryStart = sqlInjectionAnalyzer._subscriptions[1]._handler - onMysql2QueryStart({ sql: 'SELECT 1', name: 'apm:mysql2:query:start' }) + onMysql2QueryStart({ sql: 'SELECT 1' }) expect(analyze).to.be.calledOnceWith('SELECT 1') }) it('should call analyze on apm:pg:query:start', () => { - const onPgQueryStart = sqlInjectionAnalyzer._subscriptions[0]._handler + const onPgQueryStart = sqlInjectionAnalyzer._subscriptions[2]._handler - onPgQueryStart({ sql: 'SELECT 1', name: 'apm:pg:query:start' }) + onPgQueryStart({ originalText: 'SELECT 1', query: { text: 'modified-query SELECT 1' } }) expect(analyze).to.be.calledOnceWith('SELECT 1') }) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.handlebars.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.handlebars.plugin.spec.js index 2704cc2afef..5a8d702c7b0 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.handlebars.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.handlebars.plugin.spec.js @@ -2,6 +2,7 @@ const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const { SQL_ROW_VALUE } = require('../../../../src/appsec/iast/taint-tracking/source-types') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.pug.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.pug.plugin.spec.js index f07b2b57cac..fe7cf12174a 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.pug.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/template-injection-analyzer.pug.plugin.spec.js @@ -2,6 +2,7 @@ const { prepareTestServerForIast } = require('../utils') const { storage } = require('../../../../../datadog-core') +const { withVersions } = require('../../../setup/mocha') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') const { SQL_ROW_VALUE } = require('../../../../src/appsec/iast/taint-tracking/source-types') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/untrusted-deserialization-analyzer.node-serialize.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/untrusted-deserialization-analyzer.node-serialize.plugin.spec.js index 904e8df95af..233021e0d0a 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/untrusted-deserialization-analyzer.node-serialize.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/untrusted-deserialization-analyzer.node-serialize.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIast } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const { storage } = require('../../../../../datadog-core') const iastContextFunctions = require('../../../../src/appsec/iast/iast-context') const { newTaintedString } = require('../../../../src/appsec/iast/taint-tracking/operations') diff --git a/packages/dd-trace/test/appsec/iast/analyzers/unvalidated-redirect-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/unvalidated-redirect-analyzer.express.plugin.spec.js index 1ddd317a838..5d3b749eda5 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/unvalidated-redirect-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/unvalidated-redirect-analyzer.express.plugin.spec.js @@ -6,6 +6,7 @@ const path = require('path') const { UNVALIDATED_REDIRECT } = require('../../../../src/appsec/iast/vulnerabilities') const { prepareTestServerForIastInExpress } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const Axios = require('axios') describe('Unvalidated Redirect vulnerability', () => { diff --git a/packages/dd-trace/test/appsec/iast/analyzers/vulnerability-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/vulnerability-analyzer.express.plugin.spec.js index 69393c50825..76a067c3f2b 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/vulnerability-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/vulnerability-analyzer.express.plugin.spec.js @@ -6,6 +6,7 @@ const path = require('path') const { UNVALIDATED_REDIRECT } = require('../../../../src/appsec/iast/vulnerabilities') const { prepareTestServerForIastInExpress } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const axios = require('axios') describe('Vulnerability Analyzer plugin', () => { diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js index a9a995783f1..e8ee9063162 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIastInExpress } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const axios = require('axios') const { URL } = require('url') diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.fastify.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.fastify.plugin.spec.js index 26d0b94bcdf..ec1ca820b94 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.fastify.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.fastify.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIastInFastify } = require('../utils') +const { withVersions } = require('../../../setup/mocha') const axios = require('axios') const { URL } = require('url') diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server-express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server-express.plugin.spec.js index 91b6e2849f6..5d090f10796 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server-express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server-express.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const agent = require('../../../../plugins/agent') +const { withVersions } = require('../../../../setup/mocha') const { schema, resolvers, diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server.plugin.spec.js index bc6f0b7f079..ae8fe1f84d6 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/plugin.apollo-server.plugin.spec.js @@ -2,6 +2,7 @@ const path = require('path') const agent = require('../../../../plugins/agent') +const { withVersions } = require('../../../../setup/mocha') const { schema, resolvers, diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.pg.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.pg.plugin.spec.js index 69e73b0ccb0..db4a8232ba2 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.pg.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.pg.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIast } = require('../../utils') +const { withVersions } = require('../../../../setup/mocha') const connectionData = { host: '127.0.0.1', @@ -108,6 +109,6 @@ describe('db sources with pg', () => { res.end('OK') }, 'COMMAND_INJECTION', null, 'Should not detect COMMAND_INJECTION with database source') }) - }) + }, undefined, ['pg']) }) }) diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.sequelize.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.sequelize.plugin.spec.js index ae97d11bccd..5264eddb608 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.sequelize.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/sql_row.sequelize.plugin.spec.js @@ -1,6 +1,7 @@ 'use strict' const { prepareTestServerForIast } = require('../../utils') +const { withVersions } = require('../../../../setup/mocha') describe('db sources with sequelize', () => { withVersions('sequelize', 'sequelize', sequelizeVersion => { diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.cookie.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.cookie.plugin.spec.js index e6638b28309..b6f247fb0c2 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.cookie.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.cookie.plugin.spec.js @@ -6,6 +6,7 @@ const { storage } = require('../../../../../../datadog-core') const iast = require('../../../../../src/appsec/iast') const iastContextFunctions = require('../../../../../src/appsec/iast/iast-context') const { isTainted, getRanges } = require('../../../../../src/appsec/iast/taint-tracking/operations') +const { withVersions } = require('../../../../setup/mocha') const { HTTP_REQUEST_COOKIE_NAME, HTTP_REQUEST_COOKIE_VALUE diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index e357004d854..e53231d6469 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -8,6 +8,7 @@ const { storage } = require('../../../../../../datadog-core') const iast = require('../../../../../src/appsec/iast') const iastContextFunctions = require('../../../../../src/appsec/iast/iast-context') const { isTainted, getRanges } = require('../../../../../src/appsec/iast/taint-tracking/operations') +const { withVersions } = require('../../../../setup/mocha') const { HTTP_REQUEST_PATH_PARAM, HTTP_REQUEST_URI diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.fastify.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.fastify.plugin.spec.js index 2db27074a31..a82f774b9a3 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.fastify.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.fastify.plugin.spec.js @@ -7,6 +7,7 @@ const { storage } = require('../../../../../../datadog-core') const iast = require('../../../../../src/appsec/iast') const iastContextFunctions = require('../../../../../src/appsec/iast/iast-context') const { isTainted, getRanges } = require('../../../../../src/appsec/iast/taint-tracking/operations') +const { withVersions } = require('../../../../setup/mocha') const { HTTP_REQUEST_PATH_PARAM, HTTP_REQUEST_URI diff --git a/packages/dd-trace/test/appsec/iast/utils.js b/packages/dd-trace/test/appsec/iast/utils.js index a1f174bd0c3..897463a40e0 100644 --- a/packages/dd-trace/test/appsec/iast/utils.js +++ b/packages/dd-trace/test/appsec/iast/utils.js @@ -239,7 +239,7 @@ function checkVulnerabilityInRequest ( } } -function prepareTestServerForIast (description, tests, iastConfig) { +function prepareTestServerForIast (description, tests, iastConfig, pluginsToConfigure = []) { describe(description, () => { const config = {} let http @@ -254,7 +254,7 @@ function prepareTestServerForIast (description, tests, iastConfig) { }) before(() => { - return agent.load('http', undefined, { flushInterval: 1 }) + return agent.load(['http', ...pluginsToConfigure], { client: false }, { flushInterval: 1 }) .then(() => { http = require('http') }) @@ -308,7 +308,14 @@ function prepareTestServerForIast (description, tests, iastConfig) { }) } -function prepareTestServerForIastInExpress (description, expressVersion, loadMiddlewares, tests, iastConfig) { +function prepareTestServerForIastInExpress ( + description, + expressVersion, + loadMiddlewares, + tests, + iastConfig, + pluginsToConfigure = [] +) { if (arguments.length === 3) { tests = loadMiddlewares loadMiddlewares = undefined @@ -319,7 +326,7 @@ function prepareTestServerForIastInExpress (description, expressVersion, loadMid let listener, app, server before(() => { - return agent.load(['express', 'http'], { client: false }, { flushInterval: 1 }) + return agent.load(['express', 'http', ...pluginsToConfigure], { client: false }, { flushInterval: 1 }) }) before(() => { diff --git a/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js b/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js index 62c5465496f..5482fa58fd8 100644 --- a/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js @@ -6,6 +6,7 @@ const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') +const { withVersions } = require('../setup/mocha') withVersions('body-parser', 'body-parser', version => { describe('Suspicious request blocking - body-parser', () => { diff --git a/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js b/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js index fed6bbcbf45..ef03ebe45cf 100644 --- a/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js @@ -7,6 +7,7 @@ const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') +const { withVersions } = require('../setup/mocha') withVersions('cookie-parser', 'cookie-parser', version => { describe('Suspicious request blocking - cookie-parser', () => { diff --git a/packages/dd-trace/test/appsec/index.express.plugin.spec.js b/packages/dd-trace/test/appsec/index.express.plugin.spec.js index 03fbae31af4..335f5b95114 100644 --- a/packages/dd-trace/test/appsec/index.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.express.plugin.spec.js @@ -3,11 +3,12 @@ const Axios = require('axios') const { assert } = require('chai') const path = require('path') +const zlib = require('node:zlib') const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') -const zlib = require('zlib') +const { withVersions } = require('../setup/mocha') withVersions('express', 'express', version => { describe('Suspicious request blocking - path parameters', () => { diff --git a/packages/dd-trace/test/appsec/index.fastify.plugin.spec.js b/packages/dd-trace/test/appsec/index.fastify.plugin.spec.js index d169634ed06..ad28cdb4f08 100644 --- a/packages/dd-trace/test/appsec/index.fastify.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.fastify.plugin.spec.js @@ -10,6 +10,7 @@ const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') +const { withVersions } = require('../setup/mocha') withVersions('fastify', 'fastify', '>=2', (fastifyVersion, _, fastifyLoadedVersion) => { describe('Suspicious request blocking - query', () => { diff --git a/packages/dd-trace/test/appsec/index.next.plugin.spec.js b/packages/dd-trace/test/appsec/index.next.plugin.spec.js index 8f7fbf2a9d8..fca1bd2732b 100644 --- a/packages/dd-trace/test/appsec/index.next.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.next.plugin.spec.js @@ -9,6 +9,7 @@ const path = require('path') const agent = require('../plugins/agent') const { NODE_MAJOR, NODE_MINOR, NODE_PATCH } = require('../../../../version') +const { withVersions } = require('../setup/mocha') describe('test suite', () => { let server diff --git a/packages/dd-trace/test/appsec/index.sequelize.plugin.spec.js b/packages/dd-trace/test/appsec/index.sequelize.plugin.spec.js index c4623cc8be8..0c57e0c5947 100644 --- a/packages/dd-trace/test/appsec/index.sequelize.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.sequelize.plugin.spec.js @@ -5,6 +5,7 @@ const axios = require('axios') const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') +const { withVersions } = require('../setup/mocha') describe('sequelize', () => { withVersions('sequelize', 'sequelize', sequelizeVersion => { diff --git a/packages/dd-trace/test/appsec/rasp/command_injection.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/command_injection.express.plugin.spec.js index d7609367ab9..00aa2906f65 100644 --- a/packages/dd-trace/test/appsec/rasp/command_injection.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/command_injection.express.plugin.spec.js @@ -3,6 +3,7 @@ const agent = require('../../plugins/agent') const appsec = require('../../../src/appsec') const Config = require('../../../src/config') +const { withVersions } = require('../../setup/mocha') const path = require('path') const Axios = require('axios') const { checkRaspExecutedAndHasThreat, checkRaspExecutedAndNotThreat } = require('./utils') diff --git a/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js index d0a5ad8d3dd..5309e37eeff 100644 --- a/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js @@ -6,6 +6,7 @@ const fs = require('fs') const agent = require('../../plugins/agent') const appsec = require('../../../src/appsec') const Config = require('../../../src/config') +const { withVersions } = require('../../setup/mocha') const path = require('path') const { assert } = require('chai') const { checkRaspExecutedAndNotThreat, checkRaspExecutedAndHasThreat } = require('./utils') diff --git a/packages/dd-trace/test/appsec/rasp/sql_injection.mysql2.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/sql_injection.mysql2.plugin.spec.js index 2fe74e9f262..22f806c6837 100644 --- a/packages/dd-trace/test/appsec/rasp/sql_injection.mysql2.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/sql_injection.mysql2.plugin.spec.js @@ -3,6 +3,7 @@ const agent = require('../../plugins/agent') const appsec = require('../../../src/appsec') const Config = require('../../../src/config') +const { withVersions } = require('../../setup/mocha') const path = require('path') const Axios = require('axios') const { assert } = require('chai') diff --git a/packages/dd-trace/test/appsec/rasp/sql_injection.pg.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/sql_injection.pg.plugin.spec.js index 2d4dd779c17..d4264e1d4bf 100644 --- a/packages/dd-trace/test/appsec/rasp/sql_injection.pg.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/sql_injection.pg.plugin.spec.js @@ -5,6 +5,7 @@ const appsec = require('../../../src/appsec') const { wafRunFinished } = require('../../../src/appsec/channels') const addresses = require('../../../src/appsec/addresses') const Config = require('../../../src/config') +const { withVersions } = require('../../setup/mocha') const path = require('path') const Axios = require('axios') const { assert } = require('chai') diff --git a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js index b12861406c3..1a853e2022f 100644 --- a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js @@ -4,6 +4,7 @@ const Axios = require('axios') const agent = require('../../plugins/agent') const appsec = require('../../../src/appsec') const Config = require('../../../src/config') +const { withVersions } = require('../../setup/mocha') const path = require('path') const { assert } = require('chai') const { checkRaspExecutedAndNotThreat, checkRaspExecutedAndHasThreat } = require('./utils') diff --git a/packages/dd-trace/test/appsec/rule_manager.spec.js b/packages/dd-trace/test/appsec/rule_manager.spec.js index 0fcf05fb4ce..d3acd49482c 100644 --- a/packages/dd-trace/test/appsec/rule_manager.spec.js +++ b/packages/dd-trace/test/appsec/rule_manager.spec.js @@ -191,9 +191,7 @@ describe('AppSec Rule Manager', () => { }] } - assert.doesNotThrow(() => { - RuleManager.updateWafFromRC(rcConfigsForNonAsmProducts) - }) + RuleManager.updateWafFromRC(rcConfigsForNonAsmProducts) assert.strictEqual(rcConfigsForNonAsmProducts.toUnapply[0].apply_state, UNACKNOWLEDGED) assert.notProperty(rcConfigsForNonAsmProducts.toUnapply[0], 'apply_error') @@ -367,7 +365,7 @@ describe('AppSec Rule Manager', () => { id: 'asm_dd.test.failed', product: 'ASM_DD', path: 'test/rule_manager/updateWafFromRC/ASM_DD/01', - file: {} + file: { rules: [{ name: 'rule_with_missing_id' }] } } ], toModify: [], diff --git a/packages/dd-trace/test/guardrails/telemetry.spec.js b/packages/dd-trace/test/guardrails/telemetry.spec.js index 4ed8d5e3119..160ee4752ad 100644 --- a/packages/dd-trace/test/guardrails/telemetry.spec.js +++ b/packages/dd-trace/test/guardrails/telemetry.spec.js @@ -2,6 +2,8 @@ process.env.DD_INJECTION_ENABLED = 'true' +const proxyquire = require('proxyquire') +const { EventEmitter } = require('events') const { telemetryForwarder, assertTelemetryPoints } = require('../../../../integration-tests/helpers') describe('sendTelemetry', () => { @@ -16,7 +18,7 @@ describe('sendTelemetry', () => { beforeEach(() => { cleanup = telemetryForwarder() - sendTelemetry = proxyquire('../src/guardrails/telemetry', {}) + sendTelemetry = proxyquire('../../src/guardrails/telemetry', {}) }) it('should send telemetry', async () => { @@ -68,4 +70,99 @@ describe('sendTelemetry', () => { assertTelemetryPoints(process.pid, msgs, ['abort.integration', '1']) }) }) + + describe('Error scenarios and metadata', () => { + let mockProc, telemetryModule, capturedStdinData + + function createMockProcess () { + const proc = new EventEmitter() + proc.stdin = new EventEmitter() + proc.stdin.end = (data) => { + capturedStdinData = data + } + return proc + } + + function loadTelemetryModuleWithMockProc () { + return proxyquire('../../src/guardrails/telemetry', { + child_process: { spawn: () => mockProc } + }) + } + + function runTelemetry (eventType, value) { + const originalStringify = JSON.stringify + JSON.stringify = function (obj) { + if (obj && obj.metadata && obj.points) { + if (eventType === 'spawn-error') { + mockProc.emit('error', new Error(value)) + } else if (eventType === 'exit') { + mockProc.emit('exit', value) + } else if (eventType === 'stdin-error') { + mockProc.stdin.emit('error', new Error(value)) + } + } + return originalStringify.apply(this, arguments) + } + + try { + telemetryModule([{ name: 'test', tags: [] }]) + } finally { + JSON.stringify = originalStringify + } + } + + function assertStdinMetadata (expected) { + expect(capturedStdinData).to.exist + const parsed = JSON.parse(capturedStdinData) + expect(parsed.metadata.result).to.equal(expected.result) + expect(parsed.metadata.result_class).to.equal(expected.result_class) + expect(parsed.metadata.result_reason).to.equal(expected.result_reason) + } + + beforeEach(() => { + mockProc = createMockProcess() + capturedStdinData = null + telemetryModule = loadTelemetryModuleWithMockProc() + }) + + it('should set error metadata when telemetry forwarder fails to spawn', () => { + runTelemetry('spawn-error', 'Spawn failed') + + assertStdinMetadata({ + result: 'error', + result_class: 'internal_error', + result_reason: 'Failed to spawn telemetry forwarder' + }) + }) + + it('should set error metadata when telemetry forwarder exits with non-zero code', () => { + runTelemetry('exit', 1) + + assertStdinMetadata({ + result: 'error', + result_class: 'internal_error', + result_reason: 'Telemetry forwarder exited with code 1' + }) + }) + + it('should set error metadata when writing to telemetry forwarder fails', () => { + runTelemetry('stdin-error', 'Write failed') + + assertStdinMetadata({ + result: 'error', + result_class: 'internal_error', + result_reason: 'Failed to write telemetry data to telemetry forwarder' + }) + }) + + it('should set success metadata when telemetry forwarder exits successfully', () => { + runTelemetry('exit', 0) + + assertStdinMetadata({ + result: 'success', + result_class: 'success', + result_reason: 'Successfully configured ddtrace package' + }) + }) + }) }) diff --git a/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_77bd4579.yaml b/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_77bd4579.yaml new file mode 100644 index 00000000000..fb93ef7e1df --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_77bd4579.yaml @@ -0,0 +1,106 @@ +interactions: +- request: + body: "{\n \"model\": \"claude-3-5-sonnet-20241022\",\n \"temperature\": 1,\n + \ \"top_k\": -1,\n \"top_p\": -1,\n \"stream\": false,\n \"max_tokens\": + 2048,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": + \"Hello!\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '215' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - Anthropic/JS 0.14.1 + ? !!python/object/apply:multidict._multidict.istr + - anthropic-version + : - '2023-06-01' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 0.14.1 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"id":"msg_01Pnd27hsaCcokrYVGjZ3TLm","type":"message","role":"assistant","model":"claude-3-5-sonnet-20241022","content":[{"type":"text","text":"Hi + there! How can I help you today?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":9,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":13,"service_tier":"standard"}}' + headers: + CF-RAY: + - 96b7a6125975e600-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:01:22 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 4257e925-ee99-4ee8-9c62-8e53716d5203 + anthropic-ratelimit-input-tokens-limit: + - '5000000' + anthropic-ratelimit-input-tokens-remaining: + - '5000000' + anthropic-ratelimit-input-tokens-reset: + - '2025-08-07T15:01:21Z' + anthropic-ratelimit-output-tokens-limit: + - '1000000' + anthropic-ratelimit-output-tokens-remaining: + - '1000000' + anthropic-ratelimit-output-tokens-reset: + - '2025-08-07T15:01:22Z' + anthropic-ratelimit-requests-limit: + - '10000' + anthropic-ratelimit-requests-remaining: + - '9999' + anthropic-ratelimit-requests-reset: + - '2025-08-07T15:01:20Z' + anthropic-ratelimit-tokens-limit: + - '6000000' + anthropic-ratelimit-tokens-remaining: + - '6000000' + anthropic-ratelimit-tokens-reset: + - '2025-08-07T15:01:21Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CRtYt3PP5A3YUN232izWH + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - 1.1 google + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_a0f05e2e.yaml b/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_a0f05e2e.yaml new file mode 100644 index 00000000000..08f5f0e5232 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/anthropic/anthropic_v1_messages_post_a0f05e2e.yaml @@ -0,0 +1,118 @@ +interactions: +- request: + body: '{"model":"claude-3-5-sonnet-20241022","temperature":1,"top_k":-1,"top_p":-1,"stream":false,"max_tokens":2048,"thinking":{"type":"disabled"},"messages":[{"role":"user","content":"Hello!"}]}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '188' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - Anthropic/JS 0.56.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 0.56.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Timeout + : - '600' + ? !!python/object/apply:multidict._multidict.istr + - anthropic-dangerous-direct-browser-access + : - 'true' + ? !!python/object/apply:multidict._multidict.istr + - anthropic-version + : - '2023-06-01' + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"id":"msg_01HCvrgZzKLTVSWisuGYdYne","type":"message","role":"assistant","model":"claude-3-5-sonnet-20241022","content":[{"type":"text","text":"Hi + there! How can I help you today?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":9,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":13,"service_tier":"standard"}}' + headers: + CF-RAY: + - 96b7a61ebdd5f828-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:01:23 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 4257e925-ee99-4ee8-9c62-8e53716d5203 + anthropic-ratelimit-input-tokens-limit: + - '5000000' + anthropic-ratelimit-input-tokens-remaining: + - '5000000' + anthropic-ratelimit-input-tokens-reset: + - '2025-08-07T15:01:23Z' + anthropic-ratelimit-output-tokens-limit: + - '1000000' + anthropic-ratelimit-output-tokens-remaining: + - '1000000' + anthropic-ratelimit-output-tokens-reset: + - '2025-08-07T15:01:23Z' + anthropic-ratelimit-requests-limit: + - '10000' + anthropic-ratelimit-requests-remaining: + - '9999' + anthropic-ratelimit-requests-reset: + - '2025-08-07T15:01:22Z' + anthropic-ratelimit-tokens-limit: + - '6000000' + anthropic-ratelimit-tokens-remaining: + - '6000000' + anthropic-ratelimit-tokens-reset: + - '2025-08-07T15:01:23Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CRtYtBnu8AxL7pDMXnNWB + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - 1.1 google + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/genai/genai_v1beta_models_text-embedding-004_embedContent_post_ec826ebf.yaml b/packages/dd-trace/test/llmobs/cassettes/genai/genai_v1beta_models_text-embedding-004_embedContent_post_ec826ebf.yaml new file mode 100644 index 00000000000..944f3a311eb --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/genai/genai_v1beta_models_text-embedding-004_embedContent_post_ec826ebf.yaml @@ -0,0 +1,257 @@ +interactions: +- request: + body: '{"content":{"role":"user","parts":[{"text":"Hello, world!"}]},"taskType":"RETRIEVAL_DOCUMENT","title":"Document + title"}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '119' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + ? !!python/object/apply:multidict._multidict.istr + - x-goog-api-client + : - genai-js/0.24.1 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent + response: + body: + string: "{\n \"embedding\": {\n \"values\": [\n 0.015372546,\n -0.014356539,\n + \ -0.066795334,\n -0.0020824727,\n -0.012117872,\n 0.007339449,\n + \ 0.035041623,\n 0.06961974,\n 0.013370323,\n 0.05247256,\n + \ -0.03146209,\n -0.006940611,\n 0.07063284,\n 0.00054909027,\n + \ -0.01122836,\n -0.017178768,\n 0.0042083957,\n 0.016424017,\n + \ -0.09707669,\n 0.03746893,\n 0.021055471,\n -0.022875058,\n + \ 0.010626718,\n -0.02070373,\n -0.0022293816,\n -0.01637042,\n + \ 0.025992373,\n -0.009579144,\n 0.039638903,\n 0.030708546,\n + \ 0.009299081,\n 0.06436923,\n -0.0057299607,\n -0.046803296,\n + \ 0.01930405,\n 0.0032013392,\n -0.026333174,\n 0.0052720266,\n + \ 0.06508936,\n -0.062077384,\n -0.08495985,\n 0.0283871,\n + \ -0.009042779,\n 0.03720167,\n 0.00036490054,\n -0.034811813,\n + \ 0.0072823237,\n 0.02925214,\n -0.009940375,\n 0.021851901,\n + \ 0.035371892,\n 0.014777125,\n -0.08518371,\n 0.035787422,\n + \ 0.0026143282,\n 0.006970436,\n -0.038463537,\n -0.058790453,\n + \ 0.08119705,\n -0.027834713,\n -0.014108948,\n -0.030103065,\n + \ 0.023177769,\n 0.013589532,\n -0.02252272,\n -0.012253314,\n + \ 0.0062434548,\n -0.022679966,\n -0.05523718,\n 0.054588445,\n + \ -0.029449724,\n 0.01283903,\n -0.018388912,\n 0.0030332399,\n + \ -0.027272597,\n 0.0021540783,\n -0.019410372,\n -0.012342263,\n + \ 0.016839925,\n 0.023286186,\n -0.039871655,\n 0.054368377,\n + \ 0.061891507,\n 0.030754862,\n 0.004974472,\n 0.0029858174,\n + \ 0.012967229,\n -0.024814408,\n -0.031659365,\n 0.0015658811,\n + \ 0.09561799,\n 0.0076695792,\n -0.032157365,\n 0.017677005,\n + \ 0.07305761,\n -0.018521503,\n -0.10449731,\n -0.0993084,\n + \ 0.06612994,\n 0.05678272,\n -0.021122063,\n 0.006604627,\n + \ -0.0021033555,\n -0.059187435,\n 0.027671842,\n 0.049224176,\n + \ -0.05947715,\n -0.022288036,\n -0.04885982,\n 0.012646553,\n + \ -0.010028338,\n -0.04546592,\n 0.042078592,\n -0.0010533151,\n + \ -0.034670662,\n -0.041618906,\n -0.049170304,\n -0.0016108955,\n + \ -0.042815685,\n 0.032140937,\n 0.01598218,\n 0.052565727,\n + \ -0.0026933942,\n 0.05292952,\n 0.054392096,\n -0.023807887,\n + \ 0.04636691,\n 0.0020506603,\n -0.040780503,\n -0.039368436,\n + \ 0.05872356,\n -0.029384943,\n 0.03581119,\n 0.016604492,\n + \ -0.068721496,\n -0.035923947,\n 0.050974328,\n 0.01658128,\n + \ 0.016280215,\n 0.028211452,\n 0.015543086,\n -0.029721444,\n + \ -0.04587277,\n -0.007790179,\n -0.0036730971,\n -0.041662194,\n + \ 0.03500763,\n 0.061804805,\n -0.018901741,\n 0.0068170712,\n + \ -0.0026673032,\n -0.007310555,\n 0.050137825,\n -0.027147029,\n + \ -0.03009192,\n -0.010889807,\n 0.048890486,\n -0.06867036,\n + \ 0.047896713,\n 0.014029864,\n 0.035126653,\n -0.06693336,\n + \ 0.002795557,\n 0.011302215,\n -0.020045795,\n 0.010472623,\n + \ 0.00035337688,\n -0.039417088,\n 0.008782664,\n -0.015551611,\n + \ -0.037412673,\n -0.070243835,\n -0.01825607,\n -0.03991785,\n + \ 0.015088537,\n 0.0016204946,\n -0.042838525,\n 0.002073316,\n + \ -0.020200348,\n -0.02652488,\n 0.12430787,\n 0.039866142,\n + \ -0.03116778,\n -0.076160036,\n 0.044298086,\n 0.00097445317,\n + \ 0.000106458974,\n 0.027785262,\n 0.09938731,\n 0.058075245,\n + \ -0.047606893,\n 0.007952127,\n 0.008058943,\n 0.02788475,\n + \ 0.0046394216,\n -0.03134853,\n 0.051356982,\n -0.03060876,\n + \ -0.03697917,\n -0.022718553,\n 0.061371524,\n 0.0056968643,\n + \ -0.011702799,\n -0.026242746,\n 0.014163342,\n 0.04467963,\n + \ -0.04381758,\n -0.07187466,\n 0.023367116,\n 0.052662022,\n + \ -0.019537728,\n -0.025398126,\n 0.006321794,\n -0.0452083,\n + \ 0.028136032,\n 0.043413386,\n 0.09018819,\n -0.006779153,\n + \ 0.042801943,\n 0.00052895874,\n 0.058560543,\n 0.022647329,\n + \ 0.027336383,\n 0.015221506,\n 0.057680864,\n 0.042763446,\n + \ -0.041493453,\n -0.014254575,\n 0.027845612,\n -0.020551192,\n + \ -0.03357299,\n 0.01735424,\n 0.0032851237,\n 0.06925385,\n + \ -0.03473506,\n 0.018266905,\n 0.07225333,\n -0.019101856,\n + \ -0.05941022,\n 0.020929923,\n -0.00029683523,\n -0.002412333,\n + \ 0.012811295,\n 0.020340744,\n 0.020150062,\n -0.03797442,\n + \ 0.03548441,\n 0.035177212,\n 0.02204419,\n -0.06170203,\n + \ -0.04226485,\n 0.01208135,\n -0.014280069,\n -0.0043427814,\n + \ -0.06481999,\n -0.045477357,\n 0.011727481,\n -0.0076955347,\n + \ 0.017864095,\n -0.032750852,\n 0.081184,\n -0.023173213,\n + \ -0.0067123305,\n -0.09968963,\n -0.049426883,\n -0.06373506,\n + \ 0.003134508,\n -0.011172678,\n 0.012152347,\n -0.08253748,\n + \ 0.011700738,\n -0.03941561,\n -0.022462571,\n -0.02632447,\n + \ -0.031841073,\n 0.014812502,\n -0.011320272,\n 0.019756539,\n + \ -0.032569718,\n -0.06747414,\n -0.011166294,\n -0.023251815,\n + \ -0.018213738,\n 0.029349286,\n -0.006144558,\n -0.06172707,\n + \ 0.014985233,\n 0.041519944,\n -0.022628529,\n -0.0050101136,\n + \ 0.049460057,\n 0.030397618,\n 0.009922229,\n -0.027511165,\n + \ 0.027150614,\n 0.04307328,\n 0.054420575,\n 0.01662969,\n + \ 0.035029743,\n -0.0009815536,\n 0.033333708,\n 0.0429704,\n + \ -0.0128553845,\n 0.028388621,\n -0.0239162,\n -0.020408014,\n + \ 0.00806479,\n -0.032651197,\n -0.008504525,\n -0.008264416,\n + \ -0.022553155,\n 0.028487433,\n -0.0048883706,\n -0.04632972,\n + \ -0.034533385,\n -0.032373846,\n -0.15156433,\n 0.0063192244,\n + \ -0.03505422,\n -0.03754311,\n 0.061420895,\n 0.04359198,\n + \ -0.024879519,\n -0.012209626,\n 0.0025972673,\n -0.039922606,\n + \ 0.035936397,\n -0.007741664,\n 0.010022099,\n 0.00383605,\n + \ 0.019155014,\n 0.015380187,\n 0.00786512,\n -0.059654936,\n + \ 0.01300326,\n 0.05615205,\n -0.013759665,\n 0.048364237,\n + \ 0.07490698,\n 0.07062469,\n 0.017977651,\n 0.026443541,\n + \ 0.05892557,\n 0.040623315,\n -0.009612913,\n -0.037049305,\n + \ -0.0009026139,\n 0.016420903,\n 0.022297239,\n 0.0038626846,\n + \ 0.023768622,\n 0.023240793,\n 0.022049729,\n -0.05017613,\n + \ -0.058757663,\n 0.010987248,\n 0.07844332,\n -0.002116303,\n + \ 0.027087878,\n -0.016000431,\n 0.02687828,\n 0.0075496915,\n + \ -0.030942494,\n 0.036062848,\n -0.0112402355,\n -0.001397962,\n + \ -0.0071063875,\n 0.055851933,\n 0.00012305327,\n -0.054729804,\n + \ 0.01108118,\n 0.06780386,\n 0.0019702541,\n -0.01980272,\n + \ 0.008801469,\n -0.026616452,\n -0.066316105,\n 0.011260576,\n + \ 0.026288157,\n -0.034838367,\n -0.015922328,\n 0.001070962,\n + \ -0.01345126,\n -0.0032706608,\n -0.05760417,\n 0.08049015,\n + \ -0.04766306,\n 0.00116442,\n 0.017027756,\n -0.032435708,\n + \ -0.04783591,\n 0.06146502,\n 0.043359786,\n 0.037428837,\n + \ 0.029475888,\n 0.035887,\n -0.047201224,\n 0.0040941234,\n + \ 0.018292315,\n 0.015467587,\n -0.019538373,\n 0.009557745,\n + \ 0.037897006,\n -0.046477135,\n -0.0005134995,\n -0.04434762,\n + \ 0.0608216,\n 0.021251634,\n 0.034108765,\n 0.014138537,\n + \ -0.014761834,\n 0.01262709,\n -0.0043805838,\n 0.03757334,\n + \ -0.030182483,\n -0.0013276004,\n 0.0036740482,\n -0.0118292095,\n + \ -0.00820554,\n 0.0046443357,\n -0.043791953,\n -0.015316558,\n + \ 0.0016546863,\n -0.027680296,\n -0.023519056,\n -0.08480139,\n + \ -0.031160377,\n 0.032242138,\n 0.036758788,\n -0.0186116,\n + \ 0.038136326,\n 0.06590087,\n 0.010377476,\n 0.045868676,\n + \ -0.007979127,\n 0.033935122,\n -0.026578296,\n 0.01714442,\n + \ 0.0037291045,\n 0.04080839,\n -0.0113490205,\n 0.013345342,\n + \ 0.061750617,\n 0.032081265,\n -0.02896697,\n 0.05015496,\n + \ 0.027408581,\n 0.0035523302,\n -0.04051331,\n 0.0049119964,\n + \ 0.007195271,\n -0.008513754,\n -0.014407044,\n 0.0037594729,\n + \ -0.048433248,\n 0.03627755,\n 0.02047933,\n 0.04814457,\n + \ -0.022416577,\n -0.0016292968,\n -0.05458019,\n -0.012676851,\n + \ 0.07989016,\n -0.013925542,\n -0.017948713,\n -0.007656536,\n + \ -0.013178232,\n -0.0031276566,\n -0.053497545,\n 0.075117484,\n + \ 0.030977137,\n 0.0143875675,\n 0.015479171,\n 0.017094791,\n + \ -0.015213287,\n 0.012538075,\n -0.0049502794,\n 0.010894541,\n + \ 0.0011459163,\n 0.009815057,\n -0.022983788,\n 0.006193786,\n + \ 0.0038579735,\n 0.030378321,\n 0.070031516,\n -0.031084249,\n + \ -0.03679014,\n 0.0030950748,\n -0.00037366463,\n 0.0014409582,\n + \ -0.027971974,\n 0.03658044,\n 0.0031473276,\n -0.009357437,\n + \ -0.06622366,\n 0.009594193,\n 0.038741905,\n -0.036324654,\n + \ -0.029007535,\n 0.022874063,\n 0.05998333,\n 0.013395284,\n + \ -0.034259662,\n -0.040730577,\n -0.07403885,\n 0.007579807,\n + \ -0.043014023,\n -0.05534747,\n 0.01659481,\n -0.027084984,\n + \ 0.053627662,\n -0.023096714,\n 0.0552366,\n 0.044489548,\n + \ -0.024113607,\n 0.012913256,\n -0.064751014,\n -0.0016893768,\n + \ -0.058984295,\n 0.025596332,\n -0.061323587,\n -0.022068378,\n + \ 0.031427838,\n -0.022757698,\n 0.03737502,\n -0.027476246,\n + \ -0.03468243,\n 0.0014528651,\n 0.009661422,\n -0.007864442,\n + \ -0.016665876,\n 0.04695346,\n 0.034803092,\n 0.015858421,\n + \ 0.006486675,\n 0.04514477,\n 0.029479941,\n 0.038611367,\n + \ -0.053799815,\n 0.047178175,\n -0.0073030256,\n 0.0030464379,\n + \ 0.022829087,\n 0.0013512983,\n 0.039811406,\n 0.027008973,\n + \ -0.017876768,\n 0.029858569,\n -0.010673524,\n 0.05693711,\n + \ -0.015109786,\n -0.061325707,\n -0.023681398,\n 0.0009027762,\n + \ -0.012784688,\n -0.014542174,\n -0.0045905663,\n -0.04017533,\n + \ -0.02440487,\n -0.014000946,\n 0.04296293,\n -0.029839635,\n + \ 0.022849493,\n -0.03082001,\n 0.01945567,\n 0.008927605,\n + \ -0.012862118,\n 0.05917887,\n -0.018682158,\n 0.015540751,\n + \ 0.019207861,\n -0.01682097,\n 0.031962644,\n -0.038296126,\n + \ 0.001932217,\n 0.01099081,\n -0.06720687,\n -0.013287064,\n + \ 0.0070893946,\n -0.036160193,\n 0.032776173,\n -0.032740135,\n + \ 0.10128431,\n 0.016723244,\n -0.0072413767,\n 0.015263933,\n + \ -0.015489588,\n -0.011453024,\n 0.015566447,\n 0.0060613942,\n + \ -0.0016006994,\n 0.047684133,\n -0.027098129,\n -0.0016397717,\n + \ 0.013205242,\n -0.007996521,\n -0.0007076648,\n 0.04801357,\n + \ 0.009503742,\n -0.003786185,\n -0.022305058,\n -0.031149957,\n + \ 0.017355004,\n -0.014218888,\n 0.007978514,\n -0.025558528,\n + \ -0.059461165,\n -0.05193126,\n -0.0059396904,\n -0.025621835,\n + \ 0.018331062,\n 0.01582134,\n -0.039636258,\n 0.009112131,\n + \ -0.09649125,\n 0.009428427,\n -0.039126102,\n -0.014176991,\n + \ -0.029858189,\n 0.01350079,\n 0.017030425,\n 0.010435312,\n + \ 0.03928631,\n 0.014576873,\n -0.041700352,\n 0.0061138826,\n + \ 0.03326705,\n 0.011211845,\n 0.07721739,\n -0.002069592,\n + \ -0.012224608,\n 0.039730225,\n 0.06977167,\n -0.028214818,\n + \ -0.0003260362,\n -0.003943986,\n 0.0062715304,\n 0.017658157,\n + \ -0.0066731814,\n -0.06859477,\n 0.0068224524,\n 0.01758328,\n + \ 0.07000821,\n 0.04960261,\n -0.03630847,\n 0.03200622,\n + \ 0.028453141,\n 0.0054171705,\n 0.039681457,\n 0.052151036,\n + \ -0.023403153,\n 0.02829241,\n -0.013232248,\n -0.014147828,\n + \ 0.022572525,\n 0.03217797,\n -0.04284086,\n -0.03320224,\n + \ 0.02901105,\n -0.007329295,\n 0.009419848,\n -0.041918688,\n + \ -0.033295173,\n -0.006894718,\n -0.026119404,\n -0.049390603,\n + \ -0.0321733,\n -0.008851544,\n 0.0075912145,\n -0.0050543947,\n + \ 0.023912618,\n -0.0025536283,\n -0.046206612,\n -0.01240235,\n + \ -0.0010801094,\n 0.019598871,\n 0.019129664,\n -0.008976122,\n + \ 0.0020930506,\n 0.0344902,\n -0.023379175,\n -0.021851435,\n + \ 0.04741093,\n -0.085024245,\n 0.008469603,\n -0.02820444,\n + \ 0.026641995,\n -0.029730838,\n -0.010353405,\n 0.008414316,\n + \ 0.021819111,\n -0.013395014,\n -0.03856243,\n 0.014338845,\n + \ -0.0319573,\n 0.039639357,\n -0.005145333,\n 0.027996622,\n + \ -0.08056814,\n -0.0023474176,\n -0.033118267,\n 0.010456642,\n + \ -0.069546476,\n -0.003023413,\n 0.008666083,\n 0.008268043,\n + \ 0.07962353,\n 0.0066657574,\n -0.052097425,\n -0.020509822,\n + \ -0.052624073,\n 0.0044435333,\n 0.022238908,\n 0.013706352,\n + \ 0.008699113,\n 0.019698419,\n 0.021794645,\n 0.026193742,\n + \ 0.015263927,\n 0.02251332,\n -0.017144755,\n 0.0074402364,\n + \ 0.06352057,\n 0.06047737,\n 0.042586423,\n -0.005838162,\n + \ -0.034947973,\n 0.0058785905,\n -0.030345863,\n 0.08636632,\n + \ 0.020747408,\n -0.018502295,\n 0.005022302,\n 0.032919686,\n + \ -0.012183528,\n -0.020987343,\n -0.015605913,\n -0.013221538,\n + \ -0.019445335,\n -0.044257786,\n 0.011978917,\n -0.047493547,\n + \ -0.09837177,\n 0.0039054214,\n -0.00019031431,\n 0.0077924905,\n + \ -0.029862098,\n 0.035703655,\n -0.031095322,\n -0.066663854,\n + \ -0.089413315,\n -0.0012741163,\n -0.0035198391,\n 0.04912276,\n + \ 0.020241782,\n -0.022935096,\n 0.0066276626,\n -0.02495754,\n + \ 0.032729335,\n 0.002974707,\n -0.010213966,\n 0.0843644,\n + \ -0.017339956,\n 0.043281034,\n -0.045034815,\n -0.028444465,\n + \ 0.0068466268,\n 0.035945244\n ]\n }\n}\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 07 Aug 2025 15:11:59 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=234 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_audio_transcriptions_post_c8e48c61.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_audio_transcriptions_post_c8e48c61.yaml new file mode 100644 index 00000000000..20a5184ac63 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_audio_transcriptions_post_c8e48c61.yaml @@ -0,0 +1,1340 @@ +interactions: +- request: + body: !!binary | + LS0tLS0tZm9ybWRhdGEtdW5kaWNpLTA4NjM4NDk3ODI0MA0KQ29udGVudC1EaXNwb3NpdGlvbjog + Zm9ybS1kYXRhOyBuYW1lPSJtb2RlbCINCg0KZ3B0LTRvLW1pbmktdHJhbnNjcmliZQ0KLS0tLS0t + Zm9ybWRhdGEtdW5kaWNpLTA4NjM4NDk3ODI0MA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1k + YXRhOyBuYW1lPSJwcm9tcHQiDQoNCldoYXQgZG9lcyB0aGlzIHNheT8NCi0tLS0tLWZvcm1kYXRh + LXVuZGljaS0wODYzODQ5NzgyNDANCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFt + ZT0icmVzcG9uc2VfZm9ybWF0Ig0KDQpqc29uDQotLS0tLS1mb3JtZGF0YS11bmRpY2ktMDg2Mzg0 + OTc4MjQwDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9InRlbXBlcmF0dXJl + Ig0KDQowLjUNCi0tLS0tLWZvcm1kYXRhLXVuZGljaS0wODYzODQ5NzgyNDANCkNvbnRlbnQtRGlz + cG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ibGFuZ3VhZ2UiDQoNCmVuDQotLS0tLS1mb3JtZGF0 + YS11bmRpY2ktMDg2Mzg0OTc4MjQwDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5h + bWU9ImZpbGUiOyBmaWxlbmFtZT0idHJhbnNjcmlwdGlvbi5tNGEiDQpDb250ZW50LVR5cGU6IGFw + cGxpY2F0aW9uL29jdGV0LXN0cmVhbQ0KDQoAAAAcZnR5cE00QSAAAAAATTRBIGlzb21tcDQyAAAA + AW1kYXQAAAAAAAELgADQAAcA9BiucDsVDgVEY1CsNGsLzlcTWqrhzKm+sxd0kpdFZdFyVUU8rlTa + mJ/iHifBOZk+hbshwPxaR6pwkn3LuBPioiGT3JDh3ESHXegkOZckIca3RDgXKSHhvv5Dn3BiHCrp + OhNIVMwQvpIR7xGDUJ7EhDN2yeEyhBmmI5CETy+OJSAELWWIDF/4EAEwAlkTB93//yYkE0A2pn0m + APg7ywKNP03Pd7+IZ0CQCDii56bPZb/p/FxvQeGPSeBd0og054gQl0Ccvu5G/uekTQ/ELY0hGibc + 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 + AAABm8AAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAA + AAAAAAAAAAAAAAMYbWRpYQAAACBtZGhkAAAAAOCMmrPgjJqzAAC7gAABpABVxAAAAAAAMWhkbHIA + AAAAAAAAAHNvdW4AAAAAAAAAAAAAAABDb3JlIE1lZGlhIEF1ZGlvAAAAAr9taW5mAAAAEHNtaGQA + AAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAoNzdGJsAAAAZ3N0 + c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAAu4AAAAAAADNlc2RzAAAA + AAOAgIAiAAAABICAgBRAFAAYAAAD6AAAA+gABYCAgAIRiAaAgIABAgAAABhzdHRzAAAAAAAAAAEA + AABpAAAEAAAAAChzdHNjAAAAAAAAAAIAAAABAAAALgAAAAEAAAADAAAADQAAAAEAAAG4c3RzegAA + AAAAAAAAAAAAaQAAAAQAAAJ8AAACeAAAAmUAAAKZAAACsgAAAq8AAAK0AAACaAAAAm8AAAKrAAAC + twAAAngAAAKrAAACsgAAAqkAAAJiAAACrgAAAqcAAAKoAAACYAAAAm0AAAKrAAACmQAAAqsAAAKi + AAACqgAAArUAAAKwAAACrQAAArgAAAKzAAACYQAAAmkAAAJqAAACqwAAArMAAAJ/AAACfgAAAqkA + AAJtAAACgwAAAlwAAAKFAAACeQAAAnEAAAJ7AAACnAAAAmoAAAKxAAACkwAAAnQAAAJtAAACeAAA + AmYAAAKjAAACaQAAAo0AAAKVAAACdgAAArcAAAKSAAACtgAAAqMAAAKZAAACcAAAAoIAAAJrAAAC + ZgAAAl8AAAKTAAACegAAAqEAAAKFAAACpAAAAn8AAAK4AAACrQAAArMAAAK1AAACtAAAArEAAAKm + AAACoQAAAn8AAAKgAAACqgAAAqAAAAKrAAACngAAAlsAAAKxAAACYQAAAmEAAAKmAAACsAAAAp4A + AAKkAAACpwAAAqYAAAKvAAACagAAAqoAAAKoAAACswAAABxzdGNvAAAAAAAAAAMAAAAsAABzxQAA + 6dcAAAD6dWR0YQAAAPJtZXRhAAAAAAAAACJoZGxyAAAAAAAAAABtZGlyAAAAAAAAAAAAAAAAAAAA + AADEaWxzdAAAALwtLS0tAAAAHG1lYW4AAAAAY29tLmFwcGxlLmlUdW5lcwAAABRuYW1lAAAAAGlU + dW5TTVBCAAAAhGRhdGEAAAABAAAAACAwMDAwMDAwMCAwMDAwMDg0MCAwMDAwMDAwMCAwMDAwMDAw + MDAwMDE5QkMwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAw + MDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwDQotLS0tLS1mb3JtZGF0YS11bmRpY2ktMDg2Mzg0OTc4 + MjQwLS0NCg== + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '70459' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - multipart/form-data; boundary=----formdata-undici-086384978240 + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v24.5.0 + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/audio/transcriptions + response: + body: + string: '{"text":"Hello friend.","usage":{"type":"tokens","total_tokens":31,"input_tokens":26,"input_token_details":{"text_tokens":5,"audio_tokens":21},"output_tokens":5}}' + headers: + CF-RAY: + - 96e8c7433d940ca4-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Aug 2025 14:07:23 GMT + Server: + - cloudflare + Set-Cookie: + - _cfuvid=f3.KQf9jOa65SyO9cQzJ6Eh430D8i7xTHMbfkMzrslM-1755094043375-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '788' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '805' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-reset-requests: + - 2ms + x-request-id: + - req_d537c18f50204be5ad1b0b34d0e8ea8c + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_audio_translations_post_76ff1a6c.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_audio_translations_post_76ff1a6c.yaml new file mode 100644 index 00000000000..400de0a1dc8 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_audio_translations_post_76ff1a6c.yaml @@ -0,0 +1,1095 @@ +interactions: +- request: + body: !!binary | + LS0tLS0tZm9ybWRhdGEtdW5kaWNpLTAzMTkyMDQxMDgyNQ0KQ29udGVudC1EaXNwb3NpdGlvbjog + Zm9ybS1kYXRhOyBuYW1lPSJtb2RlbCINCg0Kd2hpc3Blci0xDQotLS0tLS1mb3JtZGF0YS11bmRp + Y2ktMDMxOTIwNDEwODI1DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9InJl + c3BvbnNlX2Zvcm1hdCINCg0KanNvbg0KLS0tLS0tZm9ybWRhdGEtdW5kaWNpLTAzMTkyMDQxMDgy + NQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ0ZW1wZXJhdHVyZSINCg0K + MC41DQotLS0tLS1mb3JtZGF0YS11bmRpY2ktMDMxOTIwNDEwODI1DQpDb250ZW50LURpc3Bvc2l0 + aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImZpbGUiOyBmaWxlbmFtZT0idHJhbnNsYXRpb24ubTRhIg0K + Q29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0NCg0KAAAAHGZ0eXBNNEEgAAAA + AE00QSBpc29tbXA0MgAAAAFtZGF0AAAAAAAA1jMA0AAHAOoYrnTLExqUx7V0qTVbm7yzeoKiIxUp + LVFU6XKyxMP/EQABEeztJ+DfFRLL8pIdg2JPiHcifXuDEsVx0h3XxOQ8D9mIc/5cQ8eeEyHXukEO + tdtIdk6IQ4NlbNWQmZ4jlxk9rr8E1xDjiyfBOYk+XcTJ6a0Ts6YnwHVk79snns96Hkwlohsc31CH + kDyCEl3gkY1f6YQAEgIP3j7hxZMCrc/ZeX7QBRAcHHU4s8dCkxA97vzqjxf1fY8V1yzhPbJjDrG9 + clYTbJxTtcl5R+0sP+Pl/6jsmd4ureUckanMhrjPYJ2rZx4D+hCzFEhnH5HRLOvN0FYHs9W7BaC8 + Jrlfeqwc7VmK/HUhVkJPHPg49ndziVezVTjRQOW6nL+HMISrOU7dGWWUTUyV/rmIWBnObJQ3NgBI + OB28t4zi6Vsbjdio5ITo88polzn+lkwg2VkwXsm44M+F73Pa2egklyGIObE+qLPHOvqMoiIsr1/3 + 369JvlYJZez+8+lzKN1NWRkcqlOa1Zbd3x7Ku/y5T/LTOvfOSBYJjPYN8sBCXISWdkqKRugwAigC + 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 + 8Fj1gS6R8nZa/Mjzv/tH0+9+mPF9x6ng9PIKe0dXuJZTgEeaIzS05ug+XMk7duL+z67zbsulID1l + mClXTzf0HYPw0RkHDJ4shamGBYnVW0PE8uaPcXQPWj8z5eehNCReReujze88uHm+l7C5W1fveC9s + 8RnHF3geffqOLfJ631lmHF/iPDPYtedxeOc2a/8P43ynoX7hTlcg546zpv7P1r+tf2YdTdJeScCr + /4ZBqKLeZdl95uTqOS0NUp6tvR9yFS/3hNTdHPzIlLO2rJH69vPWGadVchU1+nTHs6vXL4TdnIfU + Nr7GcsY6U3JC4bIWy3tWqxLxgM7qntUJuu2Zf6dyzHdxX8S9haRgmNnbWR+6vlsj9xwntMBleI5t + 69h9V+l+G7z0DWHHvZGwvWV+89MrcdyC/Nd0WCa4Y21+ikQAp40TvdR6lnflnbuD65tVV4aHaKKz + Dm7nZsJcJ/p6Zxnk0KICxx6gWwvcvcH/xjEdgzT/ljESDQ0Ae1YTKn/Vb5essfipZuLaWcGgDjUM + icnNbnJYZc17gzSGAEVUpNm/LEArIRIRoAeTk1FVQ31EYUFzyShp5MCGG90lmGILXM/QMapimBca + RemHnG4Z44FjR6LasLIOW8bezhc/4mvweBy+br+B1nqvQYa/pOfuPGcrrus2dReVAAAHAQYYr+ej + QSQvMprP3/Pv9frXjRxRdRBKiiZjWRkOBJlkgC6T4bMJuTghyCok/FscMnstAFvBJQGVmDCcxk4l + q3VTOpPGVZgnd9K4IUhDcSpQycWDRQSJJVaR8rR7fR41RQcwZRwFJEAPr2YEzZ7Y6e2r7BrfiH9T + 4ao2piYw/2+6+QXpc/Aug9mZd3HQAOcPkbx4rjLyGj+M86mllGWHYRGPlmuVUOafyx3v6swal2P9 + 9UqxF7pcukbave27PGRIF9S6boxxdpdy2B6PMHArcJzbclFB0JRQcrQiRiQD2DiqVAsfivTsQn4M + mBmOtzep63yYHOgvq/zNQh/v+PcUVwTiNjBkLcfl8YyXPGQBcV4flYLX9d/raI5L5Q/Jv51fM9Y/ + hyIgcuyYORM4Z3D+sdfy2YvRchl/B7qmzLtgcs679C4c++8EuLWF1T0X3TRuyJSF3xu7N/o91i+F + ycbpvw7w3n/0uTjb/9K7Gja5/W/hPnLePvHNUi+Nc5aE6k5GkZVgzl2LqrjO9Yw1RormSMoG2Jz8 + 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 + AAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAitzdGJsAAAAZ3N0c2QAAAAA + AAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAAu4AAAAAAADNlc2RzAAAAAAOAgIAi + AAAABICAgBRAFAAYAAAD6AAAA+gABYCAgAIRiAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABUAAAE + AAAAAChzdHNjAAAAAAAAAAIAAAABAAAALgAAAAEAAAACAAAAJgAAAAEAAAFkc3RzegAAAAAAAAAA + AAAAVAAAAAQAAAJ8AAACiQAAAq4AAAKiAAACpgAAAo0AAAJaAAACpQAAAqEAAAKvAAACqAAAAqwA + AAJ5AAACYwAAAl0AAAK1AAACtgAAAlQAAAKrAAACpAAAAqoAAAKsAAACsgAAAq4AAAKtAAACjAAA + AnIAAAJaAAACswAAAokAAAKiAAAChgAAAqsAAAK3AAACbQAAAmYAAAKxAAAClgAAArcAAAJrAAAC + cQAAAnYAAAK1AAACpQAAAnoAAAKwAAACjQAAAncAAAKrAAACdAAAAmEAAAKuAAACrAAAAqwAAAJw + AAACfgAAAqsAAAKfAAAChAAAAnMAAAKsAAACrgAAAnEAAAKLAAACtQAAAmIAAAKjAAACnwAAAocA + AAKmAAACrgAAArQAAAKsAAACrgAAAq8AAAKjAAACsgAAArAAAAKAAAACngAAAmQAAAJdAAACoQAA + ABhzdGNvAAAAAAAAAAIAAAAsAAB0GgAAAPp1ZHRhAAAA8m1ldGEAAAAAAAAAImhkbHIAAAAAAAAA + AG1kaXIAAAAAAAAAAAAAAAAAAAAAAMRpbHN0AAAAvC0tLS0AAAAcbWVhbgAAAABjb20uYXBwbGUu + aVR1bmVzAAAAFG5hbWUAAAAAaVR1blNNUEIAAACEZGF0YQAAAAEAAAAAIDAwMDAwMDAwIDAwMDAw + ODQwIDAwMDAwMDAwIDAwMDAwMDAwMDAwMTQ3QzAgMDAwMDAwMDAgMDAwMDAwMDAgMDAwMDAwMDAg + MDAwMDAwMDAgMDAwMDAwMDAgMDAwMDAwMDAgMDAwMDAwMDAgMDAwMDAwMDANCi0tLS0tLWZvcm1k + YXRhLXVuZGljaS0wMzE5MjA0MTA4MjUtLQ0K + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '56514' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - multipart/form-data; boundary=----formdata-undici-031920410825 + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v24.5.0 + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/audio/translations + response: + body: + string: '{"text":"Guten Tag!"}' + headers: + CF-RAY: + - 96e8c74b6c008c51-EWR + Connection: + - keep-alive + Content-Length: + - '21' + Content-Type: + - application/json + Date: + - Wed, 13 Aug 2025 14:07:24 GMT + Server: + - cloudflare + Set-Cookie: + - _cfuvid=Np0gFFKk5pk5FsbL5nhMjUDFtdQhvfwtj5gQA2JJ_tQ-1755094044861-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '1145' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-757764cbf6-z7njh + x-envoy-upstream-service-time: + - '1193' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-reset-requests: + - 6ms + x-request-id: + - req_df990790bad346e38ff99d589230dfe0 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_16a7c2e3.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_16a7c2e3.yaml new file mode 100644 index 00000000000..67fe40849df --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_16a7c2e3.yaml @@ -0,0 +1,123 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"messages\": + [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a joke about + dogs\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '236' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1wSiT9yRt9O6aajc87qHv7ZV1bS9\",\n \"object\": + \"chat.completion\",\n \"created\": 1754579092,\n \"model\": \"gpt-4-0613\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Why did the scarecrow adopt a dog?\\n\\nBecause + he needed a \\\"barking\\\" buddy!\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 13,\n \"completion_tokens\": + 19,\n \"total_tokens\": 32,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7ab3f8bfae14c-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:04:53 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=YBd9YVUR.6sTdQxgu6lxIgYpRrEIs8.pIqRZgL0qJME-1754579093-1.0.1.1-JqnGmzdTZyvfCdowe.i.yxEuMrQS6a6WUFhaqpsB7rjyRmIhFR2w3r.BxsPnzldx0u5DiclbFL1R0i1cBjAJVAtXboAQUj_ZYwTcqxUnM7I; + path=/; expires=Thu, 07-Aug-25 15:34:53 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=BMuq2zRdUyZBjaad45j9HAPPzbPow1dhzsR2Sg03VZs-1754579093503-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '1004' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1047' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999991' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_8e8c301c404c4e4da62dc23ca8f7c6ac + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_16f0ce2c.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_16f0ce2c.yaml new file mode 100644 index 00000000000..41db43d8c8b --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_16f0ce2c.yaml @@ -0,0 +1,121 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","temperature":0,"messages":[{"role":"system","content":"You + are a helpful assistant"},{"role":"user","content":"What is the weather in Tokyo?"}],"tools":[{"type":"function","function":{"name":"0","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]}}}],"tool_choice":"auto","stream":true,"stream_options":{"include_usage":true}}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '500' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_A3p8TiwUxc2MeoX8opVci8rp","type":"function","function":{"name":"0","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"location"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Tokyo"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null} + + + data: {"id":"chatcmpl-C0sCDb9fubTG31CqQJ21TeWalKEi0","object":"chat.completion.chunk","created":1754324365,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[],"usage":{"prompt_tokens":67,"completion_tokens":14,"total_tokens":81,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}} + + + data: [DONE] + + + ' + headers: + CF-RAY: + - 969f6052ac8750e5-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:25 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=L0wLs8KHB9oTYEh3_nUL0rH7z95banYrcHX.wknPG3Y-1754324365-1.0.1.1-iwuWK8HlvtLPXbxYAK3R3xmoRytuNB_6uVVA.mqs00sG3yfIZx352msHWCrMsmYISj8yA3Mu5iU85YDwWsSOCMRz2lyjVLEvVN6nopZa4q4; + path=/; expires=Mon, 04-Aug-25 16:49:25 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=CiTUOiwBRuFkeXnaH6uOIjc0TzCFMIIik5tt436Jd9Q-1754324365699-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '378' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '398' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999982' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_a156f67ac24422642670e7e431fc316e + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_25bb6f38.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_25bb6f38.yaml new file mode 100644 index 00000000000..f3f4dfa05f7 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_25bb6f38.yaml @@ -0,0 +1,106 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","temperature":0,"messages":[{"role":"system","content":"You + are a helpful assistant"},{"role":"user","content":"What is the weather in Tokyo?"},{"role":"assistant","content":"","tool_calls":[{"id":"call_5V49F97Lpy8KEEjkSXxrPzp0","type":"function","function":{"name":"0","arguments":"{\"location\":\"Tokyo\"}"}}]},{"role":"tool","tool_call_id":"call_5V49F97Lpy8KEEjkSXxrPzp0","content":"{\"location\":\"Tokyo\",\"temperature\":72}"}],"tools":[{"type":"function","function":{"name":"0","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]}}}],"tool_choice":"auto"}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '734' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C0sCCDLhKcmpHbW1EoDcKqFw1gZG7\",\n \"object\": + \"chat.completion\",\n \"created\": 1754324364,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"The current weather in Tokyo is 72\xB0F.\",\n + \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 97,\n \"completion_tokens\": 11,\n \"total_tokens\": 108,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_34a54ae93c\"\n}\n" + headers: + CF-RAY: + - 969f604d7f746529-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:25 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=2ueANsVPDICcOk8cNtWQ1t2trYc2AyyuA6EEGrL6I50-1754324365-1.0.1.1-686uZKXF9N4ZaM.p6GB.umCBwGPhGeeqKWxbdXlhm1jlp_hbPGC14mFWc6BVciV2FJt.wSuB7epckfSlch3SXEOLw9lpXo7.RxHeFBfKj5w; + path=/; expires=Mon, 04-Aug-25 16:49:25 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=41GBNLv0H5xwHbdsb1j3xJj_J.ZswwljOW3hgggXCHE-1754324365143-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '577' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '683' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999970' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_665c1ec83e2f4f7984a84df606ee6cb3 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_5cad80ab.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_5cad80ab.yaml new file mode 100644 index 00000000000..5adea048e42 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_5cad80ab.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo\",\n \"temperature\": 1,\n \"top_p\": + 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": + false,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": + \"What is 2 + 2?\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '233' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1wmZA0BqRAUqZIjNcj7qXENCUo3h\",\n \"object\": + \"chat.completion\",\n \"created\": 1754580323,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"2 + 2 = 4\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 15,\n \"completion_tokens\": 7,\n \"total_tokens\": 22,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7c94a2824826c-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:25:23 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=Xq4PYj1nXBWTT4rr7Oj6nhxHy2uW24CGWU215RObZbw-1754580323-1.0.1.1-c2O3zj6BodswKfwoyx9DWjVQxZXnNHF5WkT_W6ESD0Ng_zmLk048aTZcGXoHVLhoUGT_9uNtTnj4YFikftN803Z4ID7uLtHKXpyastN1a9A; + path=/; expires=Thu, 07-Aug-25 15:55:23 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=C7rJItXndiYQE6stZSJyMM560R.i.oYCsrY76ZGO6F8-1754580323258-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '254' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '274' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999994' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_fcba02ece9f04d3f9427a1c83960752a + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_65d65e46.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_65d65e46.yaml new file mode 100644 index 00000000000..61acb25ac86 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_65d65e46.yaml @@ -0,0 +1,137 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","max_tokens":100,"temperature":0.5,"messages":[{"role":"system","content":"You + are a helpful assistant"},{"role":"user","content":"Hello, OpenAI!"}],"stream":true,"stream_options":{"include_usage":true}}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '226' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":" + How"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":" + can"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":" + I"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":" + assist"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":" + you"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":" + today"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{"content":"?"},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null} + + + data: {"id":"chatcmpl-C0sCAeS2KU3V0Ao0aNFGE1NS6VPFl","object":"chat.completion.chunk","created":1754324362,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_62a23a81ef","choices":[],"usage":{"prompt_tokens":21,"completion_tokens":9,"total_tokens":30,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}} + + + data: [DONE] + + + ' + headers: + CF-RAY: + - 969f603ffee407ef-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:22 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=2IWzVbtsIfakfvuSDybkw.yrsyPgFRp1S3MGfHesrZg-1754324362-1.0.1.1-_Y6J1gIa.xTiltZPDbLEVzIQMcw8B5zHn9_wuL6xvhlLo2CNw70yyaxF81ld2R7ZBOcONwGQiYWMANTy0xKyQsus.WyACHv5rf.0GnSr50c; + path=/; expires=Mon, 04-Aug-25 16:49:22 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=hhVqTv9riLQ_TV8AZHpJmFuH.m2uZe_S75a6YqQNHGE-1754324362859-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '436' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '554' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999985' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_06556fb7407f4e0d9faab25e49f1e343 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_66d5df84.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_66d5df84.yaml new file mode 100644 index 00000000000..c06a546bc80 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_66d5df84.yaml @@ -0,0 +1,125 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"messages\": + [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a short joke + about {\\\"topic\\\":\\\"chickens\\\",\\\"style\\\":\\\"dad joke\\\"} in the + style of {\\\"topic\\\":\\\"chickens\\\",\\\"style\\\":\\\"dad joke\\\"}\"\n + \ }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '349' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1wS7YorkLRugTsxgsrCwcEe1LLPL\",\n \"object\": + \"chat.completion\",\n \"created\": 1754579055,\n \"model\": \"gpt-4-0613\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Why don't chickens have phones? Because + they already have eggs-change!\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 41,\n \"completion_tokens\": + 14,\n \"total_tokens\": 55,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7aa54f9dfc5ab-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:04:16 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=ODpoOD4qdRkRAHjr.DMogp4cgAe0.x7.yBorUPx_I3Q-1754579056-1.0.1.1-m9wNSm6..oIRmE2SNhTXm10xL.Rl1T1zp2oLnns3J4IMvRQL6Vre96w7ACV7uvv57YTVXcVkXi71cVMvJAyUHRleqzeFSGoUbsTxWhSBi_I; + path=/; expires=Thu, 07-Aug-25 15:34:16 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=poqzrUC84sXLprSispevgKQFHgJZzdElglrFtRUEdgE-1754579056063-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '1006' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1031' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999967' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 1ms + x-request-id: + - req_61fcef48e2c84bfe85ef8c803d6f6de5 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_705588bf.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_705588bf.yaml new file mode 100644 index 00000000000..0c8143f6c97 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_705588bf.yaml @@ -0,0 +1,104 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","max_tokens":100,"temperature":0.5,"messages":[{"role":"system","content":"You + are a helpful assistant"},{"role":"user","content":"Hello, OpenAI!"}]}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '172' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C0sC8qHr9khqbKRsSLiz0OjaOJ3T8\",\n \"object\": + \"chat.completion\",\n \"created\": 1754324360,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Hello! How can I assist you today?\",\n + \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 21,\n \"completion_tokens\": 9,\n \"total_tokens\": 30,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_34a54ae93c\"\n}\n" + headers: + CF-RAY: + - 969f60326a95e62b-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:20 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=ufFb0IQtADokHO536JmaFjRxJ_HyjSih741mRI6HzhE-1754324360-1.0.1.1-V5e9.ussPPtDp1d821IRfXzYbmmLa.WZMuKha6xA2KGH51lHpL9Rd96OeQfNkx5Q74EbNh23.oJA8e71hd9J2RlHM1K1PccZrIs1VZsdbAI; + path=/; expires=Mon, 04-Aug-25 16:49:20 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=C08kzEowaltYwE9Q7amUi25Or43vzrrGArw2CA..t10-1754324360611-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '458' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '478' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999985' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_fd8efdcd4a1a4dd3b8acef5f932ab72f + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_77aee59e.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_77aee59e.yaml new file mode 100644 index 00000000000..5328caebc9b --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_77aee59e.yaml @@ -0,0 +1,163 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","temperature":0,"messages":[{"role":"user","content":"Invent + a character for a video game"}],"tool_choice":{"type":"function","function":{"name":"json"}},"tools":[{"type":"function","function":{"name":"json","description":"Respond + with a JSON object.","parameters":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"height":{"type":"string"}},"required":["name","age","height"]}}}],"stream":true,"stream_options":{"include_usage":true}}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '489' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_4hliE4q0CzGn7F1AlrZbGMnZ","type":"function","function":{"name":"json","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"name"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Z"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ara"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" + Night"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"shade"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"age"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"28"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":",\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"height"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"5"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"''"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"7"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\\""}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null} + + + data: {"id":"chatcmpl-C0sCB3wBtGcRLkGHb1VDqcTxordnr","object":"chat.completion.chunk","created":1754324363,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_34a54ae93c","choices":[],"usage":{"prompt_tokens":65,"completion_tokens":19,"total_tokens":84,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}} + + + data: [DONE] + + + ' + headers: + CF-RAY: + - 969f6045da845001-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:23 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=3VEnoFxTCCCzS745Z.GoKGZaOdJvQAWwEiw0_XfBR9U-1754324363-1.0.1.1-RyFvohyWmC1bno0XGiwW_oK0k1Q7S6FiR2z47aOV7ykLrw2rcfVtHudjljUccnUsGDXds2dNsWprNZNWJl3xIirjPZm8gyoSntu1MMzzNMQ; + path=/; expires=Mon, 04-Aug-25 16:49:23 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=CxIGaIdxGl83gzzvBgknepv91KPaa3iptvOxbyLuxxI-1754324363383-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '145' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '157' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999987' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_cdb646ee6e859f2d577022ff605c422e + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_78f1caf1.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_78f1caf1.yaml new file mode 100644 index 00000000000..f3a696f8348 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_78f1caf1.yaml @@ -0,0 +1,109 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","temperature":0,"messages":[{"role":"user","content":"Invent + a character for a video game"}],"tool_choice":{"type":"function","function":{"name":"json"}},"tools":[{"type":"function","function":{"name":"json","description":"Respond + with a JSON object.","parameters":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"height":{"type":"string"}},"required":["name","age","height"]}}}]}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '435' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C0sC8H4t1i6R2ahId5TX8yQNs110B\",\n \"object\": + \"chat.completion\",\n \"created\": 1754324360,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_hiOEW6ZvAnrNkT4pRZjPkNWe\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"json\",\n + \ \"arguments\": \"{\\\"name\\\":\\\"Zara Nightshade\\\",\\\"age\\\":28,\\\"height\\\":\\\"5'7\\\\\\\"\\\"}\"\n + \ }\n }\n ],\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 65,\n \"completion_tokens\": + 19,\n \"total_tokens\": 84,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_34a54ae93c\"\n}\n" + headers: + CF-RAY: + - 969f60366d419c52-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:21 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=e4ZpDUJL81RNQ3Su6hSHPCgXTryaYKymk5.w1zneqCc-1754324361-1.0.1.1-WPFB4BvfQXLJeh4GU19Qa2y9xhAvVOhbaDwwzGNIIZmk4xrUkW2XiSkwfubQcBCUReXB5VlMogyZkIPP7ctuv27Pd1LPS0gCmyMq0RJ.1uY; + path=/; expires=Mon, 04-Aug-25 16:49:21 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=GZ4w0CqeLSO4ioY8pYFPfOtSWWCGaZjVh3WPppTtB1s-1754324361299-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '518' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '530' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999987' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_b3be7a24dfa54d619fad0eab3f15ccb8 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_7a82d0ef.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_7a82d0ef.yaml new file mode 100644 index 00000000000..baa9d6f2314 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_7a82d0ef.yaml @@ -0,0 +1,134 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"tools\": + [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": + \"extract_fictional_info\",\n \"description\": \"Get the fictional information + from the body of the input text\",\n \"parameters\": {\n \"type\": + \"object\",\n \"properties\": {\n \"name\": {\n \"type\": + \"string\",\n \"description\": \"Name of the character\"\n },\n + \ \"origin\": {\n \"type\": \"string\",\n \"description\": + \"Where they live\"\n }\n }\n }\n }\n }\n + \ ],\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": + \"My name is SpongeBob and I live in Bikini Bottom.\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '813' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1wJNUiGpVQ28OC3nUjZazE12GE2B\",\n \"object\": + \"chat.completion\",\n \"created\": 1754578513,\n \"model\": \"gpt-4-0613\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_nWkHMGyiVz7NcIdg4kuoCBHX\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"extract_fictional_info\",\n + \ \"arguments\": \"{\\n \\\"name\\\": \\\"SpongeBob\\\", \\n + \ \\\"origin\\\": \\\"Bikini Bottom\\\"\\n}\"\n }\n }\n + \ ],\n \"refusal\": null,\n \"annotations\": []\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 82,\n \"completion_tokens\": + 31,\n \"total_tokens\": 113,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b79d1ba90cc55a-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 14:55:14 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=OOMQLchvqxyM9jhATtQ5o8x0bpMiAvsJcSeft0obV7M-1754578514-1.0.1.1-UWTpWs_PxTzrmezAWc7lOrt5F8sg5XU7h5LuKhMmtQcTEFw2RdsTWKuVUCTEri1kqm7nhUaPxF5dRd7BiU9Kb8Ex9EyHHUvenFPc5KY9O.0; + path=/; expires=Thu, 07-Aug-25 15:25:14 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=PvTbFVpZhP5jUz.0h52JyzJEsoKIMRqTCN4qW7QVQPA-1754578514611-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '1271' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1320' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999984' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_8e8a14dab2f147b5868f338e161cb206 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_7a8ec6b9.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_7a8ec6b9.yaml new file mode 100644 index 00000000000..856f72b5014 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_7a8ec6b9.yaml @@ -0,0 +1,126 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo\",\n \"temperature\": 1,\n \"top_p\": + 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": + false,\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": + \"You are an assistant who is good at world capitals. Respond in 20 words or + fewer\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Can + you be my science teacher instead?\"\n },\n {\n \"role\": \"assistant\",\n + \ \"content\": \"Yes\"\n },\n {\n \"role\": \"user\",\n \"content\": + \"What is the powerhouse of the cell?\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '547' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1x8Ci2B6m114rWrftHMXoC2wPs9K\",\n \"object\": + \"chat.completion\",\n \"created\": 1754581664,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Mitochondria\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 54,\n \"completion_tokens\": 3,\n \"total_tokens\": 57,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7ea0c2bc60851-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:47:45 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=BaHhRiNoiYlkLH8dK5LJAlNcGLoJ1Ygvz_e.Ls3y2Wo-1754581665-1.0.1.1-IakezffWV0nrd.Mn4DSzXVxaXb5YGlMc5JrsUm4cK0N7UqbLsHMeCJwGFasBq4KiuVXq1G.q1yJKZWGuu.XO.mtDJeVA2WBMjEw2aYNtcTk; + path=/; expires=Thu, 07-Aug-25 16:17:45 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=63n9tFR8WC9i7rJ0SFVM2k5.nWCMIUsTdO2MWrgIYCg-1754581665037-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '241' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '283' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999955' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_087536d4f951414eb5ff879ea86e9df9 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_80761547.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_80761547.yaml new file mode 100644 index 00000000000..9571194f834 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_80761547.yaml @@ -0,0 +1,123 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"messages\": + [\n {\n \"role\": \"system\",\n \"content\": \"You only respond + with one word answers\"\n },\n {\n \"role\": \"user\",\n \"content\": + \"Hello!\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '312' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1vRkN33KAzwRjqPvlRngkWn42fUw\",\n \"object\": + \"chat.completion\",\n \"created\": 1754575188,\n \"model\": \"gpt-4-0613\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Hello!\",\n \"refusal\": null,\n + \ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 20,\n \"completion_tokens\": + 2,\n \"total_tokens\": 22,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b74befc8198023-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 13:59:49 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=znaOB_09gJ3GyEwAb9uNN1pwnJ1k7.YxSXebYfM.HR8-1754575189-1.0.1.1-01b6zfWnKEyMgGg6PxZ.6thXaiE3B6jEktwTgAUClvfVQzIBMSeU5L8iHbwnpDP6l1tv9UL6A7IR8emKq1oJGzPa4eBjkQULCB.fXNBYdFM; + path=/; expires=Thu, 07-Aug-25 14:29:49 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=wEOdZYEaEAi838Z1gEBTREupheMbeBDuhyqOQ5ZVkPk-1754575189424-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '879' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '918' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999986' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_d571d837b2fa49908ba47e902fa1ecc9 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_a1e12a43.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_a1e12a43.yaml new file mode 100644 index 00000000000..0db5db47a7c --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_a1e12a43.yaml @@ -0,0 +1,115 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"temperature\": 1,\n \"top_p\": + 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": + false,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": + \"Hello!\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '234' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"error\": {\n \"message\": \"This is not a chat model and + thus not supported in the v1/chat/completions endpoint. Did you mean to use + v1/completions?\",\n \"type\": \"invalid_request_error\",\n \"param\": + \"model\",\n \"code\": null\n }\n}" + headers: + CF-RAY: + - 96b749298a13c5ce-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 13:57:55 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=qhy362RcwitndbWU_Rl85BZ96NLxazWkklMdvmCYv.Y-1754575075-1.0.1.1-5pi0NeJTEBH_U0aqjR.3PEvCuThbL2jRwlBglBkrI55NqFB6xq1gXRdJsU_fUYT.W9Ib2RsJKenudoPCy0VazmwkWR2r3QMRv7ZN0Tm3vVM; + path=/; expires=Thu, 07-Aug-25 14:27:55 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=XWM6n3WRWzY26TP1ApsGwimVou_ueAQJGsfCyLBuVRA-1754575075046-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '16' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '175' + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3498' + x-ratelimit-remaining-tokens: + - '89995' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_330f402c85fd4c9687ac540b64f931d4 + status: + code: 404 + message: Not Found +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b06e073c.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b06e073c.yaml new file mode 100644 index 00000000000..30fb42e463f --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b06e073c.yaml @@ -0,0 +1,112 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","temperature":0,"messages":[{"role":"system","content":"You + are a helpful assistant"},{"role":"user","content":"What is the weather in Tokyo?"},{"role":"assistant","content":"","tool_calls":[{"id":"call_A3p8TiwUxc2MeoX8opVci8rp","type":"function","function":{"name":"0","arguments":"{\"location\":\"Tokyo\"}"}}]},{"role":"tool","tool_call_id":"call_A3p8TiwUxc2MeoX8opVci8rp","content":"{\"location\":\"Tokyo\",\"temperature\":72}"}],"tools":[{"type":"function","function":{"name":"0","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]}}}],"tool_choice":"auto","stream":true,"stream_options":{"include_usage":true}}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '788' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "data: {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"The\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" + current\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" + weather\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" + in\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" + Tokyo\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" + is\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" + \"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"72\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\xB0F\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\".\"},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":null}\n\ndata: + {\"id\":\"chatcmpl-C0sCDnRXGSTtjwYSPTeHTCX75hs1Z\",\"object\":\"chat.completion.chunk\",\"created\":1754324365,\"model\":\"gpt-4o-mini-2024-07-18\",\"service_tier\":\"default\",\"system_fingerprint\":\"fp_34a54ae93c\",\"choices\":[],\"usage\":{\"prompt_tokens\":97,\"completion_tokens\":11,\"total_tokens\":108,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"audio_tokens\":0,\"accepted_prediction_tokens\":0,\"rejected_prediction_tokens\":0}}}\n\ndata: + [DONE]\n\n" + headers: + CF-RAY: + - 969f6056997ce621-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:26 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=D3POZEFUL3.XEx5lbRTF8y4KdNI5pGGU1vxqHyUjdII-1754324366-1.0.1.1-KC72dSzu_eQPU8_CC_mDtE8Sda1wKrRY36NM9svnKwuli3V8ZisJh0YTpbIktnLfHHTFKNio5Wm5Zj3bl7k2v3D3Fk87oLYWn3iBH9pakZw; + path=/; expires=Mon, 04-Aug-25 16:49:26 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=7R6lD3qmcy7I9JPFs0dBFu3.iOitaD4XqGT.LBI.wTQ-1754324366082-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '146' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '157' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999972' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c4ab914b000e44d1afc33dd41435d256 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b637b490.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b637b490.yaml new file mode 100644 index 00000000000..e297e95fdcb --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b637b490.yaml @@ -0,0 +1,123 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo\",\n \"temperature\": 1,\n \"top_p\": + 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": + false,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": + \"Generate a JSON object with name and age.\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '260' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1wTM8wVTDFVpozkZTMW3lci7eEHr\",\n \"object\": + \"chat.completion\",\n \"created\": 1754579132,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"{\\n \\\"name\\\": \\\"John\\\",\\n + \ \\\"age\\\": 30\\n}\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 16,\n \"completion_tokens\": + 16,\n \"total_tokens\": 32,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7ac389d58b135-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:05:32 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=UVSpeuvna_iE_kHfHtGMLcnoNMoFV0b49BeeMKh_a2o-1754579132-1.0.1.1-7.apEKTPSL2n_zAFoxEDsuiB6pyrDQHkPWWLVGkgt0UzFhWykeeeiC7RUS4ifw8OZBgpuRabEZTBul7XGG8fCXE4Dmm2eSFry_jmoxk29rY; + path=/; expires=Thu, 07-Aug-25 15:35:32 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=PF1arP.cYAkKzksddBd7aT12H25Wd9oD2NO3ad1FBJQ-1754579132873-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '309' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '353' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999986' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_5e930f309c494a748421ffdf2ea5ad15 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b6ef9eaf.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b6ef9eaf.yaml new file mode 100644 index 00000000000..5555067468b --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_b6ef9eaf.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4o\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"messages\": + [\n {\n \"role\": \"user\",\n \"content\": \"What is 3 squared?\"\n + \ }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '230' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1x92WQJPHuMLQU1hcInAgpnYnyYG\",\n \"object\": + \"chat.completion\",\n \"created\": 1754581716,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"3 squared is 9.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 13,\n \"completion_tokens\": 6,\n \"total_tokens\": 19,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_07871e2ad8\"\n}\n" + headers: + CF-RAY: + - 96b7eb510c09c974-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:48:37 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=waL8ugZVUEmGkah81gI1kOMVFzu_kszadrA_kkN41sE-1754581717-1.0.1.1-TkHVs_qXhSdGyQ5odG86mjTh2zRJNhob.VRBrYlkfjVtp567Z8PfClOpjIjx2FbQPsDhlbnIfytzpAk18RDaCfhrKtjlcJYufg9HJMDrMNM; + path=/; expires=Thu, 07-Aug-25 16:18:37 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=blwcSwtKwheE1zv5f7BMpfsPGb62tgF3b01ZMqsN8Ac-1754581717069-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '344' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '357' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999993' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_4eca5663e6f04a2097cbb3d120d1f48a + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_bae5cc8f.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_bae5cc8f.yaml new file mode 100644 index 00000000000..6cb1d2f466f --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_bae5cc8f.yaml @@ -0,0 +1,126 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo\",\n \"temperature\": 1,\n \"top_p\": + 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": + false,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": + \"what country is the city Abraham Lincoln was born in Hodgenville, Kentucky. + He later lived in Springfield, Illinois, which is often associated with him + as his home city. in? respond in Spanish\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '411' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1x12S7fd8R4ucKXn5NcIPpfbvJM7\",\n \"object\": + \"chat.completion\",\n \"created\": 1754581220,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Abraham Lincoln naci\xF3 en Hodgenville, + Kentucky. M\xE1s tarde vivi\xF3 en Springfield, Illinois, que se asocia frecuentemente + con \xE9l como su ciudad natal.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 46,\n \"completion_tokens\": + 37,\n \"total_tokens\": 83,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7df33bec90575-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:40:21 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=KTHO.WDqLtMmCHeL72sv8JHSykS.p1.ocvXVsG9Mpuo-1754581221-1.0.1.1-L5BoCMdEmMsVrP4cod6qU8psZtBr_5lCZr8H4s_YigjqZo0fZfm6Xvzp9Ac5xdn1yVMI0sLN3TOZ3FWBXw4ENWCZ9wYpgzPzj9L0pVJoT2o; + path=/; expires=Thu, 07-Aug-25 16:10:21 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=aVwMyG95rLn0lux5VH.AIgS_ZZAL0dd8G7VVAPajcZ8-1754581221453-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '852' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '948' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999949' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_db51a7e5bda240f7b4607b8b5bae0b8d + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_c9652ca6.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_c9652ca6.yaml new file mode 100644 index 00000000000..b9fc13006cf --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_c9652ca6.yaml @@ -0,0 +1,110 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","temperature":0,"messages":[{"role":"system","content":"You + are a helpful assistant"},{"role":"user","content":"What is the weather in Tokyo?"}],"tools":[{"type":"function","function":{"name":"0","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]}}}],"tool_choice":"auto"}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '446' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C0sCBxHEdrPbR392VUQyCPOzwJs6m\",\n \"object\": + \"chat.completion\",\n \"created\": 1754324363,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_5V49F97Lpy8KEEjkSXxrPzp0\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"0\",\n + \ \"arguments\": \"{\\\"location\\\":\\\"Tokyo\\\"}\"\n }\n + \ }\n ],\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 67,\n \"completion_tokens\": + 14,\n \"total_tokens\": 81,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_34a54ae93c\"\n}\n" + headers: + CF-RAY: + - 969f604a1c4e0592-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:24 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=9NrvKEQZ48PWkkYGL2cCRhabfp_ows2AfQi9T_BlWL4-1754324364-1.0.1.1-xOWMJrPEGG1Q3GTgte3rioQeFUW3K5yKfuiXm3FQNy6FUZJ6B.NHAaBjPkHV5J3GyrcC2OZ5GXP0HMDzn2qAA_RqxA27NZOQsKN9gG9ZzEc; + path=/; expires=Mon, 04-Aug-25 16:49:24 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=93ACqP6xQLv3LqiMDMJVJw4VlyvDXGe01lzdRsG3GpE-1754324364337-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '402' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '414' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999982' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_8a448e3f94cb4a4c9bba45b723251c01 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_d7728303.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_d7728303.yaml new file mode 100644 index 00000000000..4ad52cf3a03 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_d7728303.yaml @@ -0,0 +1,125 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"messages\": + [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a short joke + about [object Object] in the style of [object Object]\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '285' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1wS4JHzEErrtv7PhADNgmgGr3FIq\",\n \"object\": + \"chat.completion\",\n \"created\": 1754579052,\n \"model\": \"gpt-4-0613\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"As an AI, I need more specific entries + for anthropomorphized objects or individuals to make a joke in your requested + format. Please replace [object Object] with the desired topic and individual's + style.\",\n \"refusal\": null,\n \"annotations\": []\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 25,\n \"completion_tokens\": 41,\n + \ \"total_tokens\": 66,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7aa46ac8f5794-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:04:14 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=CC8.wfzcYFv9m8ezIMHUENr0bb12G5bL4J.3tlUqkiM-1754579054-1.0.1.1-2AACjocg3nFYOQMlW35DBaahIBDD7BtpNmhOHXDZiSr3MZAvKTKX4HZdzskBnxseCVz69s1LJABtNEq2JPuug.qSFHrHzyKPweZWdmTdhOA; + path=/; expires=Thu, 07-Aug-25 15:34:14 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=ofwLxhcP2zpgBpaJ112FRIS5GSlSWBwPp8hQkr4RX_M-1754579054580-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '1753' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1772' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999979' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 1ms + x-request-id: + - req_2594f166017b43d28f95ae41bab252ee + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_f4bcbfe2.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_f4bcbfe2.yaml new file mode 100644 index 00000000000..700a40b5086 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_f4bcbfe2.yaml @@ -0,0 +1,123 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"messages\": + [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a joke about + chickens\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '240' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1wSjUtyoLAtR9X5itqUQhpXXfYUh\",\n \"object\": + \"chat.completion\",\n \"created\": 1754579093,\n \"model\": \"gpt-4-0613\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Why don't chickens use Facebook?\\n\\nBecause + they already know what everyone's clucking about!\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 13,\n \"completion_tokens\": 18,\n \"total_tokens\": 31,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7ab46e9933b74-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:04:54 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=27kdxiHaWX.BCtjygGv0N8Q5FkO_ru.qfeJtH01WHrk-1754579094-1.0.1.1-DodLUMhL_eTNVgV9qqaSG2XYp.nP1asFVMgbxXumTjGbyAeKy9Py4tNp7c8BkkOqEKbXSVOkXM6.ZKlcweiT3TUnn9wLUBXzwFq5wM.QXis; + path=/; expires=Thu, 07-Aug-25 15:34:54 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=6wzDxs.XXMsJ5r7tpKevfBK.dYR1Rai1Y26x_G5JuCI-1754579094854-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '968' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1003' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999990' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c13879e80f6c46d9a8cdd7721ea07847 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_f5eb021a.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_f5eb021a.yaml new file mode 100644 index 00000000000..573d12d972c --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_f5eb021a.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-4\",\n \"temperature\": 1,\n \"top_p\": 1,\n \"frequency_penalty\": + 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": false,\n \"messages\": + [\n {\n \"role\": \"user\",\n \"content\": \"Hello!\"\n }\n + \ ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '217' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1vQZhzSwTNVjmNjtIEQpxDeKgKpS\",\n \"object\": + \"chat.completion\",\n \"created\": 1754575115,\n \"model\": \"gpt-4-0613\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Hello! How can I assist you today?\",\n + \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 9,\n \"completion_tokens\": 9,\n \"total_tokens\": 18,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b74a28ee3dc94e-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 13:58:36 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=gmrAgyTWZf7NF19bybWVtksfYNJnwIM9DLWBMjMzsk8-1754575116-1.0.1.1-Vs0K7DbrZg0HT2KnpfLrwK2_YHQ.2kkGk.WqLii07e50NPm.25MM8ZZobEvfN8m4nNjmzWceGqbkpVOk6jWPx88Lu_ZwhfnRl0xWXTNCFqU; + path=/; expires=Thu, 07-Aug-25 14:28:36 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=atIreOTwHHoED4o4wUUuPo0hj_ARUGOgCSqNqiRtKAA-1754575116546-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '781' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '803' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999996' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_91c871b050554dd89d6f1291e013fdf0 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_ff33eb0d.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_ff33eb0d.yaml new file mode 100644 index 00000000000..fef472f0481 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_chat_completions_post_ff33eb0d.yaml @@ -0,0 +1,124 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo\",\n \"temperature\": 1,\n \"top_p\": + 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": 0,\n \"n\": 1,\n \"stream\": + false,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": + \"what is the city Abraham Lincoln is from?\"\n }\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '260' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-C1x11bMQv2lqCbnebaAWHZzU1aDne\",\n \"object\": + \"chat.completion\",\n \"created\": 1754581219,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Abraham Lincoln was born in Hodgenville, + Kentucky. He later lived in Springfield, Illinois, which is often associated + with him as his home city.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 16,\n \"completion_tokens\": + 30,\n \"total_tokens\": 46,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": null\n}\n" + headers: + CF-RAY: + - 96b7df2caf7a0a91-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:40:19 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=py2CGUKTp3JaDWKhsK0E6sadGg6WURKcjz9OcPl1xho-1754581219-1.0.1.1-pU3cumunMHOv6fx40zzGIBR6.f5G8s3ZEMRGhoUgtbCZ0Ful5CfirLGHTpjQsNjYu54Ca0ZgVP9_6.kHAqGJR1wlIAEkZLn7fN0l1ENzcAc; + path=/; expires=Thu, 07-Aug-25 16:10:19 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=AVEz0Ct6XWXbu8YX.ZIA0vDknqmvYn3ZfF86mKivmCw-1754581219856-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '472' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '496' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999987' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_9ddad1bb1c7d4785b6c0348afc7beaf9 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_0dcaaf7e.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_0dcaaf7e.yaml new file mode 100644 index 00000000000..ed045d12e3a --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_0dcaaf7e.yaml @@ -0,0 +1,132 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"temperature\": 0.7,\n + \ \"max_tokens\": 256,\n \"top_p\": 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": + 0,\n \"n\": 1,\n \"stream\": false,\n \"prompt\": [\n \"System: You are + a world class technical documentation writer\\nHuman: Can you tell me about + LangSmith?\"\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '303' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"id\": \"cmpl-C1wv3A4pgdxOEH1Ws7Nye2dnwvF9I\",\n \"object\": + \"text_completion\",\n \"created\": 1754580849,\n \"model\": \"gpt-3.5-turbo-instruct:20230824-v2\",\n + \ \"choices\": [\n {\n \"text\": \"\\n\\nSystem: LangSmith is a top-of-the-line + software that caters to the needs of technical writers like you. It offers + a user-friendly interface, advanced formatting tools, and collaboration features + to help you create high-quality technical documents with ease. With LangSmith, + you can produce professional-looking manuals, guides, and tutorials that will + impress even the most discerning clients. Its robust features and intuitive + design make it the go-to tool for technical writers all over the world.\",\n + \ \"index\": 0,\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 21,\n \"completion_tokens\": + 94,\n \"total_tokens\": 115\n }\n}\n" + headers: + CF-RAY: + - 96b7d6248cf58797-IAD + Cache-Control: + - no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:34:10 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=K9..ho56q1yzWrwN9lNqx0gYKMvSgU4Y9Uqa6GZGbn0-1754580850-1.0.1.1-tX7senOGRI4QHLpRL.SCoWjAVQIjiWLDxLDVtBT4qPQBU4n7lq0zlKQNy3MFCRZ2zVPr3ZTyoLhY6SDEfB2uSzt_bXR0nnZx82L5d6tjUOA; + path=/; expires=Thu, 07-Aug-25 16:04:10 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=0wPL3MvpRq_mu1abbgVQr0JKJn.QmSxTur7SW1LtUZ8-1754580850296-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - gpt-3.5-turbo-instruct:20230824-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '849' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-667b55c4ff-2thzh + x-envoy-upstream-service-time: + - '871' + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3499' + x-ratelimit-remaining-tokens: + - '89974' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 17ms + x-request-id: + - req_460f9cea41574528b35c927f909e5e9b + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_1d81e88f.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_1d81e88f.yaml new file mode 100644 index 00000000000..318aad39cca --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_1d81e88f.yaml @@ -0,0 +1,125 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"temperature\": 0.7,\n + \ \"max_tokens\": 256,\n \"top_p\": 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": + 0,\n \"n\": 1,\n \"stream\": false,\n \"prompt\": [\n \"What is 2 + 2?\"\n + \ ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '216' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"id\": \"cmpl-C1whAyd0wdeojF5CZgTfS4arCthTX\",\n \"object\": + \"text_completion\",\n \"created\": 1754579988,\n \"model\": \"gpt-3.5-turbo-instruct:20230824-v2\",\n + \ \"choices\": [\n {\n \"text\": \"\\n\\n4\",\n \"index\": 0,\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 8,\n \"completion_tokens\": 2,\n + \ \"total_tokens\": 10\n }\n}\n" + headers: + CF-RAY: + - 96b7c11999e64f16-IAD + Cache-Control: + - no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:19:48 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=s4TYT.dkzOmepTjhVM9bJnryKsIeDPMAmU4x9IxUG9E-1754579988-1.0.1.1-P5OhBleni6KGoDEPl473AQ0_BZcoPz4uINcM7ZSTDBKjNRiNK_wqFyos6WGdl3p0_XeoevHLLMuS2LK7za9qXX9NXofR0kG6wiSyYxOD2ws; + path=/; expires=Thu, 07-Aug-25 15:49:48 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=JRXf38AXMS_Dh7waFPjsOr_KcLLRyvFzKShesFlSOcA-1754579988283-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - gpt-3.5-turbo-instruct:20230824-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '744' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-7fc4f5b456-r4822 + x-envoy-upstream-service-time: + - '775' + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3498' + x-ratelimit-remaining-tokens: + - '89995' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_42412a9f29ad4593854a198d1c8e662c + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_209fa61a.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_209fa61a.yaml new file mode 100644 index 00000000000..0e3659315e0 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_209fa61a.yaml @@ -0,0 +1,125 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"temperature\": 0.7,\n + \ \"max_tokens\": 256,\n \"top_p\": 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": + 0,\n \"n\": 1,\n \"stream\": false,\n \"prompt\": [\n \"what is 2 + 2?\"\n + \ ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '216' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"id\": \"cmpl-C1vN7r95PXzdMhUsQFdIAbIxTepDM\",\n \"object\": + \"text_completion\",\n \"created\": 1754574901,\n \"model\": \"gpt-3.5-turbo-instruct:20230824-v2\",\n + \ \"choices\": [\n {\n \"text\": \"\\n\\n2 + 2 is equal to 4.\",\n + \ \"index\": 0,\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 8,\n \"completion_tokens\": + 11,\n \"total_tokens\": 19\n }\n}\n" + headers: + CF-RAY: + - 96b744eb2f163956-IAD + Cache-Control: + - no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 13:55:01 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=PgO9sMeUX_mCzY9KJH0lkfbLZ4aeP94ZJp0.Yz6kZQA-1754574901-1.0.1.1-hHXzTYKQbegLVcalry_bT6E8y7bGnYNqPZA9vofK9bdwafiVaTT2pODTTFfsjwuPkwpAS75Xim2dHDnMu2Hek810LuuLrmWBV_1_go2SJxY; + path=/; expires=Thu, 07-Aug-25 14:25:01 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=a96r_RuBcmglb4q96CAd2oqYYRHjCrzWnMksI24L3b8-1754574901833-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - gpt-3.5-turbo-instruct:20230824-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '600' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-667b55c4ff-m4nj8 + x-envoy-upstream-service-time: + - '621' + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3498' + x-ratelimit-remaining-tokens: + - '89995' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_38b55ee856ca4df2aa07676a06c47edc + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_44214830.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_44214830.yaml new file mode 100644 index 00000000000..10374c98284 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_44214830.yaml @@ -0,0 +1,127 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"temperature\": 0.7,\n + \ \"max_tokens\": 256,\n \"top_p\": 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": + 0,\n \"n\": 2,\n \"stream\": false,\n \"prompt\": [\n \"what is 2 + 2?\"\n + \ ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '216' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"id\": \"cmpl-C1vO9voH4vUAq1GGSUJYWuttIbjcs\",\n \"object\": + \"text_completion\",\n \"created\": 1754574965,\n \"model\": \"gpt-3.5-turbo-instruct:20230824-v2\",\n + \ \"choices\": [\n {\n \"text\": \"\\n\\n2 + 2 is equal to 4.\",\n + \ \"index\": 0,\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ },\n {\n \"text\": \"\\n\\n2 + 2 is 4.\",\n \"index\": 1,\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 8,\n \"completion_tokens\": 20,\n + \ \"total_tokens\": 28\n }\n}\n" + headers: + CF-RAY: + - 96b7467bfc7a399a-IAD + Cache-Control: + - no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 13:56:05 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=27OHyTatUTv2cpjJFk8Xts0X94MWg28Mcirkj8vy9Ws-1754574965-1.0.1.1-HgocaUr9ILNuPOu2yIeONSpZlqNCwmZSGLcly_aREClIJKObeqotU1LsHGmknv.wGksDeT7xshlB_GJI5XeyjBt8vMP99YGdCbNyxbUAIUU; + path=/; expires=Thu, 07-Aug-25 14:26:05 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=jqu5F7mBu243YgTsFOsOzwiNnvUJsK7s0VBRgGfBh_I-1754574965539-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - gpt-3.5-turbo-instruct:20230824-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '323' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-769c5f49f9-4zbjv + x-envoy-upstream-service-time: + - '362' + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3498' + x-ratelimit-remaining-tokens: + - '89995' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 3ms + x-request-id: + - req_daca03c47d9447428deaf3f73730005e + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_4ce6ce50.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_4ce6ce50.yaml new file mode 100644 index 00000000000..09510c7b135 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_4ce6ce50.yaml @@ -0,0 +1,94 @@ +interactions: +- request: + body: "{\n \"model\": \"text-embedding-3-small\",\n \"temperature\": 0.7,\n + \ \"max_tokens\": 256,\n \"top_p\": 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": + 0,\n \"n\": 1,\n \"stream\": false,\n \"prompt\": [\n \"what is 2 + 2?\"\n + \ ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '216' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"error\": {\n \"message\": \"You are not allowed to + sample from this model\",\n \"type\": \"invalid_request_error\",\n + \ \"param\": null,\n \"code\": null\n }\n}\n" + headers: + CF-RAY: + - 96b7478e1a67e5fe-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 07 Aug 2025 13:56:49 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=HqJvbnc4jx3rySJZQdWOW0x2ylDyUQFaEnug16PL08c-1754575009-1.0.1.1-wdiTgPJJGxvbRl6KhV5TwFACzBUEN39VB73M9JvuBFDT59y88OnIVuGbp5nAgmwpydu7nOMIf7iiddxZYznBLrGQwnXTu6LezaYgnYcB92M; + path=/; expires=Thu, 07-Aug-25 14:26:49 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=87O2qwt7Q_KwFBBvyX02eWEyTX0fuLrYuYfQvQbWUgQ-1754575009316-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + vary: + - Origin + x-envoy-upstream-service-time: + - '9' + x-request-id: + - req_75fc69ecf07e4ee1bc6d76ee358e6bdd + status: + code: 403 + message: Forbidden +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_83d892db.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_83d892db.yaml new file mode 100644 index 00000000000..7a0532065e6 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_83d892db.yaml @@ -0,0 +1,127 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"temperature\": 0.7,\n + \ \"max_tokens\": 256,\n \"top_p\": 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": + 0,\n \"n\": 1,\n \"stream\": false,\n \"prompt\": [\n \"what is 2 + 2?\",\n + \ \"what is the circumference of the earth?\"\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '263' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"id\": \"cmpl-C1vNj3w07OzBZJ7WOnhCqm1q713Mn\",\n \"object\": + \"text_completion\",\n \"created\": 1754574939,\n \"model\": \"gpt-3.5-turbo-instruct:20230824-v2\",\n + \ \"choices\": [\n {\n \"text\": \"\\n\\n2 + 2 is equal to 4.\",\n + \ \"index\": 0,\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ },\n {\n \"text\": \"\\n\\nThe circumference of the Earth is approximately + 24,901 miles (40,075 kilometers).\",\n \"index\": 1,\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 16,\n \"completion_tokens\": 30,\n \"total_tokens\": 46\n }\n}\n" + headers: + CF-RAY: + - 96b745dbbb1639a6-IAD + Cache-Control: + - no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 13:55:39 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=0UYox2n_S0TVt2QSrMXtp.cq9ZVKrHcclJN5OJmeb3A-1754574939-1.0.1.1-h11E8sJ0IlhTuoVS3br8n4v9nwxO617xVzvafWO2IM8c6UfRuR2kg2ch_Jjy4Dnh7oY1P5TpyTLdtkyMly8DUY3K3L8xIN4EJq5xGsfeSPM; + path=/; expires=Thu, 07-Aug-25 14:25:39 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=K9HPfR3.SUPh_87nRFd8XJwEC8ZpoD6StNFZfKzHxJE-1754574939983-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - gpt-3.5-turbo-instruct:20230824-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '394' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-56b8ccc9b7-rx9tf + x-envoy-upstream-service-time: + - '440' + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3498' + x-ratelimit-remaining-tokens: + - '89985' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 10ms + x-request-id: + - req_34bfb9bc05d84eb18068c518aa55435e + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_8cb403e7.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_8cb403e7.yaml new file mode 100644 index 00000000000..0f2d16eabba --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_8cb403e7.yaml @@ -0,0 +1,144 @@ +interactions: +- request: + body: '{"model":"gpt-3.5-turbo-instruct","n":1,"stream":false,"prompt":["what + is 2 + 2?"]}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '83' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 5.12.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 5.12.0 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + ? !!python/object/apply:multidict._multidict.istr + - traceparent + : - 00-6894cc460000000066806b48287a8fdc-0dc3d62e18cf7a69-01 + ? !!python/object/apply:multidict._multidict.istr + - tracestate + : - dd=t.dm:-0;t.tid:6894cc4600000000;s:1;p:0dc3d62e18cf7a69 + ? !!python/object/apply:multidict._multidict.istr + - x-datadog-parent-id + : - '991871836424731241' + ? !!python/object/apply:multidict._multidict.istr + - x-datadog-sampling-priority + : - '1' + ? !!python/object/apply:multidict._multidict.istr + - x-datadog-tags + : - _dd.p.tid=6894cc4600000000,_dd.p.dm=-0 + ? !!python/object/apply:multidict._multidict.istr + - x-datadog-trace-id + : - '7386021346548551644' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"id\": \"cmpl-C1xF1vxS3VOSOdaoXue851BkDHxM3\",\n \"object\": + \"text_completion\",\n \"created\": 1754582087,\n \"model\": \"gpt-3.5-turbo-instruct:20230824-v2\",\n + \ \"choices\": [\n {\n \"text\": \"\\n\\n2 + 2 = 4\",\n \"index\": + 0,\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 8,\n \"completion_tokens\": + 8,\n \"total_tokens\": 16\n }\n}\n" + headers: + CF-RAY: + - 96b7f45a8dc8c979-IAD + Cache-Control: + - no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:54:47 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=bNee2y5j4xNYBwMAa3kgxA52FFAVpL9q92_gn3Z6IPM-1754582087-1.0.1.1-_w2sYQ4cxK16vM43kiISvsMZQi_Ye0qJ1XEMV.mdi59QbxKcWsVHWl70zpl4Mj.ckDKT074Z5LXMJjA9XBI_Tlsu8bARF.59DDmnh15SbF4; + path=/; expires=Thu, 07-Aug-25 16:24:47 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=BdELrZ2Ico.hIxVaS7tSEcqpaQmEyWjFxnZhzRjWdtU-1754582087490-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - gpt-3.5-turbo-instruct:20230824-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '551' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-6557b57797-t6vqc + x-envoy-upstream-service-time: + - '631' + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3498' + x-ratelimit-remaining-tokens: + - '89995' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_f73d011afff248d59a9cfd889c5a8f4e + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_df4d2c81.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_df4d2c81.yaml new file mode 100644 index 00000000000..4a57726caa1 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_completions_post_df4d2c81.yaml @@ -0,0 +1,93 @@ +interactions: +- request: + body: "{\n \"model\": \"text-embedding-3-small\",\n \"temperature\": 0.7,\n + \ \"max_tokens\": 256,\n \"top_p\": 1,\n \"frequency_penalty\": 0,\n \"presence_penalty\": + 0,\n \"n\": 1,\n \"stream\": false,\n \"prompt\": [\n \"Hello!\"\n ]\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '208' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/completions + response: + body: + string: "{\n \"error\": {\n \"message\": \"You are not allowed to + sample from this model\",\n \"type\": \"invalid_request_error\",\n + \ \"param\": null,\n \"code\": null\n }\n}\n" + headers: + CF-RAY: + - 96b7de3d9e1af83a-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 07 Aug 2025 15:39:41 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=OFYwF6cVi3Pv7L7knV5hQXA3W7xof_NMi.C8_bh9AJo-1754581181-1.0.1.1-TQmPOyAz69ZEYA00QMY8B0rRy9axaz2YWfWPmCh2ypTiSnKdV9Lz5zHRX0s9UQnVFnvDXo0bFl79YzHU8NWMc107HG3NIRGFZeBiXBT8oLY; + path=/; expires=Thu, 07-Aug-25 16:09:41 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=rG0zVGUYwgXeS87RSetdCHK1wvVncxgN5Z5bbrWpXA0-1754581181124-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + vary: + - Origin + x-envoy-upstream-service-time: + - '7' + x-request-id: + - req_2c156b3433534647ae4cc109367a0886 + status: + code: 403 + message: Forbidden +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_0381abe4.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_0381abe4.yaml new file mode 100644 index 00000000000..2eb81128e76 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_0381abe4.yaml @@ -0,0 +1,872 @@ +interactions: +- request: + body: '{"model":"text-embedding-ada-002","input":["hello world","goodbye world"],"encoding_format":"float"}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '100' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": [\n -0.014910275,\n + \ 0.0013435053,\n -0.018532472,\n -0.031137712,\n -0.024248956,\n + \ 0.0074287946,\n -0.022958137,\n -0.00097223034,\n -0.012750129,\n + \ -0.022470787,\n 0.025763692,\n 0.010833658,\n -0.033034425,\n + \ -0.0038362348,\n 0.0058218567,\n 0.013856546,\n 0.019467657,\n + \ -0.022852764,\n 0.01836124,\n 0.011321009,\n -0.006193955,\n + \ 0.008752543,\n 0.0066417903,\n -0.008074204,\n -0.014712702,\n + \ -0.010860002,\n 0.018334897,\n -0.013606285,\n 0.01925691,\n + \ -0.039172404,\n 0.0025799915,\n -0.0047483696,\n -0.017755346,\n + \ -0.014857589,\n 0.010366066,\n -0.009325508,\n 0.00015404623,\n + \ -0.018255867,\n 0.02420944,\n -0.01974426,\n 0.009325508,\n + \ -0.0013006977,\n 0.017465571,\n -0.02175952,\n -0.03811867,\n + \ 0.005891008,\n 0.0016299882,\n -0.024459701,\n -0.0020745303,\n + \ 0.03385107,\n 0.029530775,\n -0.004587017,\n -0.02095605,\n + \ 0.008811815,\n -0.019757433,\n 0.020284297,\n -0.02818727,\n + \ 0.021509258,\n 0.02721257,\n -0.020244783,\n 0.0025618803,\n + \ 0.009351851,\n -0.019862805,\n 0.014488784,\n 0.0074353805,\n + \ 0.008172991,\n 0.009957746,\n 0.0027150004,\n -0.011288079,\n + \ 0.005219255,\n 0.020363327,\n 0.0126249995,\n -0.015476655,\n + \ -0.008416666,\n 0.019480828,\n -0.0007586031,\n -0.025552945,\n + \ -0.0056572114,\n 0.0016662101,\n 0.004037102,\n 0.03848748,\n + \ -0.033034425,\n -0.009608698,\n 0.0054826876,\n 0.017913405,\n + \ 0.007725156,\n -0.007830529,\n 0.024130411,\n -0.014040949,\n + \ -0.016477698,\n 0.0053740214,\n 0.0035991457,\n 0.008811815,\n + \ 0.0066714264,\n -0.011070748,\n 0.0060754106,\n -0.010267279,\n + \ 0.016780647,\n 0.00013161331,\n -0.030242043,\n -0.01924374,\n + \ -0.012743544,\n -0.015173708,\n -0.00958894,\n -0.012829159,\n + \ -0.00995116,\n 0.0069743735,\n 0.0036650037,\n 0.02347183,\n + \ 0.005476102,\n -0.015964005,\n 0.018558815,\n -0.008877673,\n + \ -0.031427488,\n -0.008522039,\n -0.004656168,\n -0.007474895,\n + \ -0.004913015,\n -0.016135236,\n -0.022220526,\n 0.007949074,\n + \ 0.024301643,\n 0.022088809,\n -0.017939748,\n 0.008522039,\n + \ 0.0039481935,\n -0.04833985,\n -0.010484611,\n -0.008317879,\n + \ -0.018045122,\n 0.037802555,\n 0.0064145797,\n 0.024709962,\n + \ -0.011202464,\n -0.028793165,\n 0.015542514,\n -0.021258997,\n + \ 0.018795904,\n -0.023379628,\n -0.019889148,\n -0.001585534,\n + \ 0.025711006,\n 0.01576643,\n -0.0055913534,\n -0.003622196,\n + \ -0.0023363163,\n 0.013566771,\n 0.0038526992,\n 0.0019806826,\n + \ -0.011801773,\n -0.001811098,\n 0.0056209895,\n 0.004840571,\n + \ 0.009496739,\n 0.020047208,\n 0.017188966,\n -0.003197411,\n + \ 0.02688328,\n 0.0046792184,\n -0.009957746,\n 0.00074831274,\n + \ 0.0028417774,\n 0.0063882363,\n -0.03485211,\n 0.006994131,\n + \ 0.03485211,\n 0.023537688,\n 0.009569183,\n 0.004988752,\n + \ -0.026698876,\n -0.00889743,\n 0.013369196,\n -0.036169272,\n + \ 0.016635759,\n -0.00022885692,\n 0.0051270537,\n 0.010866588,\n + \ 0.032138757,\n -0.0062861564,\n -0.015740087,\n -0.027739435,\n + \ 0.02323474,\n 0.007856872,\n 0.02787115,\n -0.0030179478,\n + \ -0.0027117075,\n 0.0072114626,\n 0.0028022625,\n 0.0071456046,\n + \ -0.00787663,\n 0.013046491,\n 0.034404274,\n 0.02266836,\n + \ 0.014567814,\n -0.68913925,\n -0.010530711,\n 0.009970917,\n + \ 0.00966797,\n 0.009700899,\n 0.017570943,\n 0.010583398,\n + \ 0.019546686,\n -0.00582515,\n 0.024025038,\n -0.0027100611,\n + \ 0.012941118,\n 0.00083310506,\n -0.013500912,\n -0.0086010685,\n + \ -0.009812858,\n 0.00792273,\n -0.021430228,\n -0.019941835,\n + \ 0.024393843,\n -0.010398995,\n 0.02633007,\n -0.02380112,\n + \ -0.0056967265,\n -0.00075695664,\n 0.005041438,\n -0.00012245492,\n + \ -0.016530385,\n -0.014831246,\n 0.04104277,\n -0.020271126,\n + \ 0.011347352,\n 0.009937989,\n 0.0008660341,\n 0.059904534,\n + \ 0.017557772,\n -0.01616158,\n 0.007883215,\n 0.010701942,\n + \ 0.039672922,\n -0.014146321,\n -0.01722848,\n 0.004349928,\n + \ -0.00962187,\n 0.001931289,\n 0.013072834,\n 0.008140062,\n + \ -0.0093716085,\n -0.006289449,\n -0.0046890974,\n 0.031848982,\n + \ -0.006470559,\n 0.018176837,\n 0.010352895,\n -0.0070863324,\n + \ -0.010267279,\n 0.021324854,\n -0.006602275,\n -0.002945504,\n + \ 0.013790688,\n -0.00323034,\n 0.018229524,\n -0.0010068058,\n + \ 0.0052719414,\n -0.014304381,\n 0.016280124,\n -0.030637192,\n + \ 0.0060029663,\n 0.0037143973,\n -0.0058943005,\n 0.00090719544,\n + \ 0.0051632756,\n -0.025552945,\n -0.012196922,\n 0.0136458,\n + \ 0.03582681,\n 0.016991392,\n -0.013777516,\n -0.0052587697,\n + \ 0.028635105,\n 0.020402841,\n 0.00800176,\n -0.019006649,\n + \ -0.0135272555,\n 0.017465571,\n -0.015911318,\n -0.03208607,\n + \ -0.0046133604,\n -0.0060358956,\n -0.00045689062,\n + \ 0.03738106,\n 0.007856872,\n -0.017610459,\n -0.02721257,\n + \ 0.027502345,\n 0.0040601525,\n -0.005844907,\n 0.0047187335,\n + \ 0.020692617,\n -0.0070402315,\n -0.0042939484,\n 0.010445096,\n + \ -0.007692227,\n 0.007652712,\n 0.038382106,\n -0.0039877086,\n + \ -0.0046429965,\n 0.025803206,\n 0.017821204,\n -0.01787389,\n + \ 0.0052818204,\n 0.0004163467,\n -0.028477045,\n 0.013856546,\n + \ 0.0065561747,\n -0.029873237,\n 0.0025487088,\n 0.021087766,\n + \ 0.024499215,\n -0.009661384,\n 0.0323495,\n -0.016978221,\n + \ 0.028108241,\n 0.0009845787,\n -0.0017551186,\n 0.015858632,\n + \ -0.007165362,\n -0.008917187,\n -0.00958894,\n -0.016596243,\n + \ 0.017333854,\n -0.004781299,\n 0.013388953,\n -0.013579941,\n + \ 0.013118935,\n -0.010603155,\n -0.0051237606,\n -0.015344939,\n + \ 0.005920644,\n 0.0069809593,\n -0.008047861,\n -0.0074683093,\n + \ -0.008298121,\n -0.0013303338,\n 0.0144097535,\n -0.026198355,\n + \ -0.023076681,\n -0.005291699,\n 0.005324628,\n 0.00013346557,\n + \ 0.003029473,\n -0.010221179,\n -0.03735472,\n 0.027080854,\n + \ -0.006651669,\n -0.0048866714,\n 0.0065627606,\n -0.024459701,\n + \ -0.01494979,\n -0.029135628,\n 0.004774713,\n 0.013540427,\n + \ -0.015542514,\n 0.009997261,\n -0.0045376234,\n -0.032138757,\n + \ -0.023669403,\n 0.020442357,\n 0.001141815,\n -0.038540166,\n + \ 0.003823063,\n 0.0031793,\n -0.011630542,\n 0.004972287,\n + \ -0.002701829,\n 0.022786906,\n -0.013540427,\n -0.0044026147,\n + \ 0.004152354,\n -0.0135272555,\n 0.015964005,\n -0.0069480306,\n + \ -0.019836461,\n 0.013415297,\n 0.01023435,\n 0.001331157,\n + \ 0.0023412558,\n 0.031164056,\n -0.014897104,\n 0.0022342363,\n + \ 0.0120783765,\n 0.0055024447,\n -0.012750129,\n -0.0019955006,\n + \ 0.0026491424,\n -0.011393453,\n 0.009720657,\n -0.0021831964,\n + \ 0.017860718,\n 0.013790688,\n 0.05026291,\n 0.0065825176,\n + \ 0.028898537,\n -0.02582955,\n -0.0042676055,\n -0.024630932,\n + \ -0.0060391882,\n -0.01892762,\n 0.0175446,\n 0.014199008,\n + \ 0.003444379,\n -0.018651016,\n 0.00515669,\n 0.016833331,\n + \ 0.009457224,\n 0.033561293,\n -0.0037506192,\n -0.0046594613,\n + \ -0.018321725,\n 0.003763791,\n 0.008633998,\n 0.006882172,\n + \ -0.010089462,\n -0.008146648,\n 0.015740087,\n 0.02818727,\n + \ 0.008495696,\n 0.044309333,\n 0.008673512,\n -0.01015532,\n + \ -0.028608762,\n 0.0064903167,\n 0.014396583,\n 0.004870207,\n + \ -0.002127217,\n 0.007988589,\n 0.011037819,\n -0.030321073,\n + \ 0.030321073,\n -0.0052587697,\n -0.0055320812,\n 0.0074353805,\n + \ 0.032296818,\n -0.025408057,\n -0.0054102438,\n 0.03126943,\n + \ 0.018163666,\n -0.008765714,\n 0.001720543,\n 0.042597026,\n + \ -0.012052034,\n -0.0021453279,\n -0.017478742,\n 0.008792058,\n + \ 0.009411124,\n -0.034799423,\n 0.0032500976,\n -0.0026507888,\n + \ 0.036248304,\n 0.0254344,\n 0.023919664,\n 0.008054446,\n + \ 0.0035793881,\n -0.017162623,\n -0.0006939798,\n -0.021140452,\n + \ 0.0116503,\n -0.008153234,\n -0.00082734245,\n 0.001247188,\n + \ -0.008522039,\n 0.0009343619,\n 0.015832288,\n -0.023564031,\n + \ 0.011485654,\n -0.0038889213,\n 0.0012035569,\n 0.0034180358,\n + \ 0.008923774,\n 0.004129303,\n -0.021970265,\n -0.02282642,\n + \ 0.0016373972,\n 0.0077053984,\n 0.00086109474,\n -0.03029473,\n + \ -0.03962024,\n 0.002472972,\n 0.000887438,\n 0.0036353676,\n + \ -0.017821204,\n 0.0027084146,\n 0.024327984,\n -0.025895407,\n + \ 0.007270735,\n 0.005390486,\n 0.026053468,\n -0.0034674294,\n + \ 0.000009685772,\n -0.012163992,\n 0.015173708,\n 0.0006594043,\n + \ -0.009167449,\n -0.024947051,\n 0.010833658,\n -0.00036757055,\n + \ -0.0104253385,\n -0.02331377,\n -0.0019115316,\n -0.000374568,\n + \ -0.021482915,\n 0.0012430717,\n -0.015608371,\n -0.010352895,\n + \ 0.009015975,\n 0.001862138,\n -0.010175077,\n 0.009812858,\n + \ 0.029952267,\n -0.009411124,\n -0.0063092066,\n -0.018018778,\n + \ -0.023195226,\n -0.01307942,\n 0.048366193,\n 0.011018061,\n + \ -0.0057856347,\n 0.004705562,\n -0.003444379,\n -0.006151147,\n + \ -0.04728612,\n -0.022312727,\n 0.0022243576,\n -0.008159819,\n + \ 0.0025503552,\n -0.007698813,\n -0.0038856284,\n 0.018097809,\n + \ 0.025473915,\n 0.005604525,\n 0.0156874,\n -0.022655189,\n + \ -0.008917187,\n 0.010655842,\n -0.0042906557,\n -0.006467266,\n + \ -0.00497558,\n 0.016978221,\n 0.020903364,\n -0.0027874445,\n + \ 0.008390323,\n 0.015371283,\n -0.0069151013,\n -0.0075670965,\n + \ 0.003908679,\n 0.0024038209,\n 0.01973109,\n 0.011986176,\n + \ 0.017175794,\n 0.031875324,\n 0.014343896,\n 0.022273213,\n + \ 0.006888758,\n -0.019480828,\n 0.007270735,\n 0.009174034,\n + \ -0.0040469808,\n -0.009964332,\n 0.010063119,\n 0.0131321065,\n + \ -0.014119978,\n 0.0127172,\n 0.0028565954,\n -0.037565466,\n + \ 0.032533906,\n -0.001680205,\n -0.0049327724,\n -0.015924491,\n + \ -0.013843374,\n 0.016385498,\n -0.020086722,\n -0.0118544595,\n + \ -0.012111306,\n -0.004662754,\n -0.000032568892,\n -0.014607328,\n + \ 0.0067636278,\n 0.00043919127,\n -0.026935967,\n -0.023629889,\n + \ -0.030400103,\n 0.019994522,\n -0.016767474,\n 0.001247188,\n + \ -0.014449269,\n -0.023669403,\n -0.014515127,\n -0.0028779993,\n + \ 0.021179967,\n 0.010504368,\n -0.014251694,\n 0.0022177717,\n + \ 0.010339723,\n 0.01197959,\n -0.009490154,\n -0.03298174,\n + \ 0.0018226231,\n -0.003149664,\n 0.009002803,\n 0.008594483,\n + \ 0.008357394,\n 0.008258606,\n -0.012368153,\n 0.023511345,\n + \ 0.013606285,\n 0.01084683,\n 0.015305424,\n -0.015397626,\n + \ 0.026053468,\n -0.00091707415,\n 0.008298121,\n -0.0055123237,\n + \ -0.007955659,\n 0.0030146549,\n -0.0024219318,\n -0.011479069,\n + \ -0.007527582,\n 0.00084956957,\n 0.0030558163,\n 0.008752543,\n + \ 0.023208397,\n 0.026264213,\n -0.022154666,\n -0.0165699,\n + \ 0.030742565,\n -0.026633019,\n 0.022931794,\n -0.007112676,\n + \ 0.0009755232,\n 0.024802163,\n 0.011656885,\n 0.03922509,\n + \ 0.003585974,\n -0.0165699,\n -0.0070204744,\n -0.032691963,\n + \ -0.004076617,\n 0.021140452,\n 0.030136669,\n 0.0064639733,\n + \ 0.005255477,\n -0.01859833,\n -0.0055386666,\n -0.013619456,\n + \ -0.015990349,\n 0.013270409,\n -0.0042379694,\n -0.018874934,\n + \ -0.021706833,\n -0.012776473,\n -0.015779603,\n 0.006259813,\n + \ -0.0033176022,\n -0.01803195,\n -0.019520342,\n -0.009549426,\n + \ 0.009351851,\n -0.004764834,\n -0.04004173,\n -0.03909337,\n + \ -0.012032276,\n -0.00031673635,\n -0.0065693464,\n 0.009325508,\n + \ -0.0019510464,\n -0.00028936405,\n -0.014844418,\n -0.01713628,\n + \ 0.0061906623,\n -0.012071791,\n 0.0030607556,\n -0.012157407,\n + \ 0.032428533,\n 0.0132638225,\n 0.034325246,\n 0.009187206,\n + \ 0.012987219,\n 0.013632628,\n -0.008344222,\n -0.013566771,\n + \ -0.00438615,\n -0.00205148,\n -0.015661057,\n 0.012420839,\n + \ 0.036617108,\n 0.026053468,\n -0.0072180484,\n 0.013764344,\n + \ -0.002695243,\n -0.0029356251,\n -0.0062729847,\n 0.009009389,\n + \ -0.022115152,\n -0.017491912,\n -0.02332694,\n 0.0029257464,\n + \ -0.002953736,\n -0.000679985,\n 0.015990349,\n 0.0058218567,\n + \ 0.01641184,\n 0.021338027,\n 0.018545642,\n -0.0037736695,\n + \ 0.0321651,\n -0.022431271,\n 0.017267996,\n 0.00040749705,\n + \ 0.010741457,\n -0.002752869,\n -0.0035069443,\n -0.004178697,\n + \ 0.0034641365,\n 0.012697443,\n 0.025131455,\n 0.020560902,\n + \ -0.010405581,\n -0.010715114,\n -0.010418752,\n 0.027133541,\n + \ -0.0026096276,\n -0.0035629235,\n 0.005456344,\n -0.01819001,\n + \ 0.005943694,\n -0.023840634,\n 0.008271778,\n -0.013033319,\n + \ 0.002571759,\n -0.0065166596,\n -0.024011865,\n 0.017333854,\n + \ -0.013896061,\n -0.029109284,\n -0.0053377994,\n 0.0045079873,\n + \ 0.044019558,\n 0.0047615413,\n 0.013270409,\n 0.011880803,\n + \ 0.007099504,\n -0.008179577,\n 0.010240936,\n -0.018545642,\n + \ -0.0061807833,\n 0.028160926,\n 0.01446244,\n -0.0042873626,\n + \ -0.010128977,\n 0.00987213,\n 0.0020975808,\n -0.014396583,\n + \ -0.02989958,\n 0.008298121,\n 0.023037165,\n -0.0065825176,\n + \ -0.023261083,\n 0.012262779,\n -0.046811942,\n 0.023366457,\n + \ -0.0042247977,\n -0.012348395,\n -0.018387584,\n -0.0038329419,\n + \ -0.02226004,\n 0.007659298,\n -0.03825039,\n 0.019375455,\n + \ 0.018703703,\n -0.016293297,\n -0.010148735,\n -0.00024141112,\n + \ -0.018071465,\n 0.0018917741,\n -0.011940075,\n 0.015160536,\n + \ -0.020165753,\n 0.006928273,\n 0.021970265,\n 0.010385823,\n + \ -0.028582418,\n -0.00074584305,\n 0.008528625,\n 0.014883933,\n + \ -0.027054511,\n -0.0051237606,\n -0.0059041795,\n 0.0107217,\n + \ 0.013869718,\n 0.0074353805,\n -0.008640584,\n -0.006365186,\n + \ -0.009878716,\n -0.004929479,\n 0.019507172,\n 0.004550795,\n + \ 0.001469459,\n -0.014976134,\n -0.010082876,\n -0.024169926,\n + \ -0.03933046,\n 0.0038033058,\n 0.002352781,\n -0.018400755,\n + \ -0.014251694,\n -0.012895018,\n -0.0034575507,\n 0.018005606,\n + \ 0.007837115,\n -0.015858632,\n 0.00913452,\n 0.030637192,\n + \ -0.0185193,\n -0.0064540943,\n -0.0033011376,\n 0.009852373,\n + \ -0.027265256,\n 0.012809402,\n -0.0059305225,\n -0.025816377,\n + \ 0.0007849463,\n -0.0185193,\n -0.009779929,\n 0.0015361403,\n + \ 0.015555685,\n 0.013685315,\n 0.015265909,\n -0.0002718705,\n + \ 0.029030254,\n 0.020639932,\n 0.003859285,\n -0.008317879,\n + \ -0.013566771,\n 0.027976524,\n -0.016108893,\n 0.00039494285,\n + \ -0.00672082,\n -0.0032797337,\n 0.0025371837,\n -0.005624282,\n + \ -0.0026935965,\n -0.0060951677,\n -0.03176995,\n -0.0152000515,\n + \ 0.0012628292,\n 0.007863458,\n -0.005232427,\n -0.0031628357,\n + \ -0.033824723,\n -0.01543714,\n -0.029109284,\n 0.010919274,\n + \ 0.011558098,\n -0.024090895,\n 0.013738001,\n 0.0025305978,\n + \ -0.00138549,\n 0.0058646644,\n -0.006187369,\n -0.005700019,\n + \ -0.02721257,\n -0.00623347,\n -0.007165362,\n -0.014396583,\n + \ -0.0024812042,\n 0.021575116,\n 0.001349268,\n -0.0051698615,\n + \ -0.018176837,\n -0.0104253385,\n -0.027528688,\n -0.020099895,\n + \ -0.014870761,\n 0.0055123237,\n 0.010589983,\n 0.01584546,\n + \ -0.0072048767,\n 0.023656232,\n 0.007520996,\n 0.009174034,\n + \ 0.009832615,\n -0.021601459,\n 0.015055164,\n -0.0020119653,\n + \ 0.012407667,\n 0.0077514993,\n -0.025895407,\n -0.0032715015,\n + \ 0.015502999,\n 0.0117227435,\n -0.010583398,\n 0.017281167,\n + \ 0.0061906623,\n -0.009529668,\n -0.012019104,\n -0.004738491,\n + \ -0.003375228,\n 0.0076066116,\n 0.023735262,\n -0.010609741,\n + \ 0.005196205,\n -0.0014373532,\n -0.00038547572,\n -0.00072649727,\n + \ -0.0002465563,\n 0.011913732,\n -0.011136606,\n 0.018901277,\n + \ -0.018637845,\n -0.011162949,\n 0.001982329,\n -0.021430228,\n + \ 0.033008084,\n -0.019375455,\n 0.007889802,\n 0.037749868,\n + \ -0.0005046378,\n -0.001618463,\n -0.0019543394,\n -0.009174034,\n + \ -0.012117892,\n 0.003116735,\n -0.002695243,\n -0.02029747,\n + \ -0.01859833,\n -0.0059634517,\n -0.00966797,\n -0.037170317,\n + \ 0.0054793945,\n -0.020824334,\n -0.0053542643,\n 0.0022046003,\n + \ 0.03150652,\n 0.013961919,\n -0.03192801,\n -0.011011476,\n + \ -0.015055164,\n 0.012170578,\n 0.0027841516,\n -0.010873173,\n + \ 0.007297078,\n -0.004004173,\n 0.00087920576,\n 0.020060379,\n + \ -0.012815988,\n -0.008443009,\n 0.0076790554,\n 0.014383411,\n + \ 0.003892214,\n 0.03266562,\n 0.21548773,\n -0.01820318,\n + \ -0.010636085,\n 0.033587635,\n 0.0116964,\n 0.01680699,\n + \ 0.0037012256,\n 0.0039119716,\n 0.0010891284,\n 0.023353284,\n + \ -0.020363327,\n 0.0064903167,\n -0.021338027,\n 0.0036024384,\n + \ 0.0032072898,\n -0.0019000064,\n -0.019928664,\n -0.025368543,\n + \ -0.036696136,\n -0.030426446,\n 0.0019329354,\n -0.022444444,\n + \ -0.03379838,\n -0.021864891,\n 0.021891234,\n -0.0040140515,\n + \ -0.015476655,\n -0.008857915,\n 0.036327332,\n 0.012335223,\n + \ -0.00200044,\n -0.016438184,\n 0.011676642,\n 0.011742501,\n + \ -0.018953964,\n -0.0069151013,\n 0.02542123,\n -0.004514573,\n + \ 0.04183307,\n 0.011558098,\n -0.010431924,\n -0.016938705,\n + \ -0.0032385725,\n -0.006579225,\n -0.005038145,\n 0.011893975,\n + \ 0.00833105,\n -0.033034425,\n 0.0243675,\n 0.02633007,\n + \ -0.012308881,\n 0.0102936225,\n 0.026777906,\n 0.031796295,\n + \ -0.0013319802,\n -0.0028565954,\n 0.0062104193,\n 0.010398995,\n + \ -0.0063520144,\n -0.00082734245,\n -0.005215962,\n 0.039910015,\n + \ 0.0010150381,\n 0.03192801,\n -0.017267996,\n 0.0027001824,\n + \ -0.01973109,\n -0.0061313896,\n 0.0054464657,\n -0.021785863,\n + \ -0.010754629,\n -0.0059535727,\n 0.006994131,\n -0.0072904923,\n + \ -0.024617761,\n -0.02381429,\n 0.01787389,\n 0.0073168357,\n + \ 0.026698876,\n 0.032454874,\n 0.004738491,\n -0.010287036,\n + \ 0.0124735255,\n -0.023629889,\n -0.039119717,\n -0.032138757,\n + \ -0.01108392,\n 0.012177164,\n -0.0058943005,\n -0.016504042,\n + \ -0.0034871867,\n -0.024538731,\n -0.0023083268,\n -0.0011525169,\n + \ 0.024709962,\n 0.026356414,\n 0.0036584178,\n 0.015568856,\n + \ -0.018545642,\n 0.015239566,\n -0.020073552,\n -0.0120783765,\n + \ 0.0023807706,\n -0.0024812042,\n 0.0030360587,\n 0.018348068,\n + \ -0.010188249,\n -0.0022572866,\n -0.0026639604,\n -0.0049163075,\n + \ -0.012025691,\n -0.015239566,\n 0.010003846,\n -0.0042215046,\n + \ -0.003826356,\n 0.0100301895,\n -0.01981012,\n -0.009793101,\n + \ 0.00456726,\n -0.0120783765,\n 0.005252184,\n -0.010985132,\n + \ -0.006710941,\n -0.0028714135,\n -0.0031002704,\n -0.0037308617,\n + \ -0.012690857,\n 0.0077976,\n 0.0071390187,\n -0.041516952,\n + \ -0.0008314586,\n -0.005499152,\n 0.0175446,\n 0.0035234087,\n + \ 0.0005750236,\n -0.0061478545,\n 0.013514084,\n 0.00014725461,\n + \ -0.023063509,\n 0.0031776538,\n -0.0004313706,\n -0.011762258,\n + \ -0.009272821,\n 0.001294935,\n -0.009937989,\n -0.007270735,\n + \ 0.038987998,\n -0.016438184,\n -0.013988262,\n 0.0015838875,\n + \ -0.030900624,\n -0.013882889,\n -0.003684761,\n -0.017926577,\n + \ 0.026553988,\n -0.018545642,\n -0.028318986,\n -0.029188313,\n + \ 0.009490154,\n -0.002887878,\n -0.014686358,\n 0.014857589,\n + \ 0.0323495,\n -0.012032276,\n -0.03832942,\n -0.022220526,\n + \ -0.1715472,\n 0.022879107,\n 0.0076395404,\n -0.02315571,\n + \ 0.024565075,\n 0.018703703,\n 0.029135628,\n 0.004972287,\n + \ -0.0020053794,\n 0.0033472383,\n -0.0016225792,\n -0.013896061,\n + \ -0.031954356,\n -0.010287036,\n -0.0009615284,\n -0.014528299,\n + \ 0.012289123,\n 0.020587245,\n 0.038935315,\n 0.026356414,\n + \ 0.02681742,\n -0.016925534,\n 0.020982392,\n -0.0068426575,\n + \ 0.0067833853,\n -0.014633671,\n 0.0062071267,\n 0.028635105,\n + \ 0.001494979,\n -0.009654799,\n -0.01884859,\n -0.017570943,\n + \ 0.02095605,\n 0.0072509777,\n 0.016543556,\n -0.0091279335,\n + \ 0.0053443853,\n -0.030847937,\n -0.0019378748,\n 0.026593504,\n + \ 0.026856937,\n 0.012684272,\n 0.010971961,\n -0.022141496,\n + \ -0.012335223,\n 0.0065594674,\n 0.020534558,\n 0.0072180484,\n + \ 0.015134193,\n 0.0066714264,\n 0.016859675,\n -0.011070748,\n + \ -0.004425665,\n 0.005495859,\n 0.020692617,\n 0.012243022,\n + \ 0.0066187396,\n 0.024907537,\n 0.0074024512,\n -0.017320681,\n + \ -0.0063750646,\n -0.021720003,\n -0.00022350595,\n -0.009088418,\n + \ -0.00018697529,\n 0.0003998822,\n -0.009924817,\n 0.0101421485,\n + \ -0.018005606,\n 0.014594156,\n -0.0054431725,\n -0.029214656,\n + \ -0.010774386,\n -0.032402188,\n 0.024196269,\n 0.004596896,\n + \ -0.030637192,\n 0.021904407,\n -0.012269366,\n -0.003447672,\n + \ -0.004277484,\n 0.022128325,\n 0.0018407342,\n 0.010385823,\n + \ -0.00925965,\n 0.008146648,\n -0.0034904797,\n 0.009556011,\n + \ -0.02721257,\n -0.014014605,\n 0.017913405,\n -0.0146731865,\n + \ -0.0014422926,\n -0.016688444,\n 0.0044388366,\n 0.011505411,\n + \ 0.0059535727,\n 0.014752216,\n -0.014752216,\n 0.0014241816,\n + \ -0.004372978,\n 0.0066648405,\n -0.0055254954,\n 0.0088645015,\n + \ 0.03103234,\n -0.0017831082,\n 0.027476002,\n 0.020007692,\n + \ 0.018334897,\n -0.029504431,\n -0.025526602,\n 0.01446244,\n + \ 0.020890191,\n 0.020903364,\n 0.01567423,\n 0.027686749,\n + \ -0.011459311,\n -0.018532472,\n 0.002607981,\n -0.0063783578,\n + \ 0.03345592,\n -0.0018374412,\n -0.034483306,\n 0.0010899517,\n + \ 0.0034740153,\n 0.005749413,\n -0.06696452,\n -0.033376887,\n + \ 0.008061033,\n 0.014159493,\n -0.008548383,\n 0.030004954,\n + \ -0.0015427262,\n 0.0025470622,\n 0.00016166107,\n 0.018255867,\n + \ 0.010583398,\n -0.026949137,\n -0.019006649,\n -0.009332093,\n + \ 0.018901277,\n -0.02347183,\n -0.011235394,\n -0.0023099731,\n + \ -0.020929707,\n 0.026079811,\n -0.007863458,\n -0.011538341,\n + \ 0.008370565,\n -0.004211626,\n -0.02584272,\n 0.0043762713,\n + \ -0.013020148,\n 0.03680151,\n 0.0140541205,\n -0.00035460474,\n + \ -0.016464528,\n -0.005314749,\n -0.006467266,\n -0.000006662988,\n + \ -0.0011862692,\n 0.0034970655,\n -0.042702395,\n 0.031005997,\n + \ 0.011847873,\n -0.029609805,\n 0.041148145,\n 0.010471439,\n + \ -0.0062729847,\n -0.044546425,\n -0.0023659526,\n 0.00031406086,\n + \ 0.002626092,\n 0.040147103,\n 0.0034180358,\n -0.020073552,\n + \ -0.019388627,\n -0.029214656,\n -0.026672533,\n 0.0050315596,\n + \ 0.024657276,\n -0.0038065987,\n 0.018795904,\n 0.03946218,\n + \ -0.01543714,\n 0.008232264,\n 0.018743217,\n 0.0017798153,\n + \ -0.02283959,\n 0.011907145,\n -0.0070270603,\n 0.00026796016,\n + \ -0.017926577,\n 0.0113275945,\n 0.023063509,\n 0.009141105,\n + \ -0.015924491,\n 0.029267343,\n -0.00938478,\n 0.006045774,\n + \ -0.028477045,\n 0.00058654876,\n -0.0045112805,\n -0.017939748,\n + \ 0.014989305,\n -0.017676316,\n -0.03790793,\n -0.017979264,\n + \ -0.0063915295,\n -0.0057527055,\n 0.027581375,\n 0.013211137,\n + \ -0.0042445553,\n 0.007415623,\n -0.0033851068,\n -0.030426446,\n + \ -0.022325898,\n 0.026619848,\n 0.0120981345,\n -0.0070402315,\n + \ 0.010319966,\n 0.0011796834,\n -0.009200377,\n 0.005367436,\n + \ 0.013632628,\n 0.01624061,\n -0.017847547,\n -0.012335223,\n + \ -0.06490975,\n 0.026619848,\n -0.013066249,\n -0.014067292,\n + \ 0.01502882,\n 0.002575052,\n 0.00759344,\n -0.020560902,\n + \ -0.006714234,\n -0.014106806,\n -0.03208607,\n -0.0031595428,\n + \ -0.008508868,\n 0.0010660781,\n -0.033587635,\n -0.005993088,\n + \ 0.02802921,\n 0.012618413,\n 0.019678403,\n 0.014594156,\n + \ 0.010326551,\n -0.029925924,\n -0.009345265,\n 0.0065825176,\n + \ -0.015924491,\n 0.014027777,\n -0.025684662,\n 0.00420504,\n + \ -0.005159983,\n -0.011716157,\n -0.0008215799,\n -0.03693323,\n + \ -0.0020975808,\n 0.02672522,\n 0.008021518,\n -0.00069562625,\n + \ 0.00084874633,\n 0.022800077,\n 0.004359807,\n 0.030663535,\n + \ -0.03922509,\n -0.017162623,\n 0.013461397,\n -0.00095082645,\n + \ 0.0036584178,\n 0.0045705526,\n -0.007837115,\n -0.0036254888,\n + \ 0.021008736,\n 0.007870044,\n -0.0013015209,\n 0.027423317,\n + \ -0.016609415,\n -0.024578245,\n -0.019191053,\n -0.017360197,\n + \ -0.0055814744,\n -0.0007623076,\n 0.00297514,\n -0.03385107,\n + \ 0.020574072,\n -0.008396909,\n 0.020416014,\n 0.001047144,\n + \ 0.0009961039,\n -0.005838321,\n -0.017518256,\n -0.008792058,\n + \ -0.0062367627,\n -0.021245826,\n -0.010069705,\n -0.010998304,\n + \ 0.0005145165,\n 0.013276994,\n 0.025447574,\n 0.01397509,\n + \ -0.016477698,\n 0.023972351,\n -0.036511734,\n 0.024090895,\n + \ 0.016108893,\n 0.016148409,\n -0.022431271,\n 0.010695357,\n + \ 0.032033384,\n 0.018348068,\n 0.00007388456,\n -0.0075670965,\n + \ -0.00418199,\n 0.010254107,\n -0.030584505,\n -0.013514084,\n + \ 0.0047088545,\n 0.021377541,\n -0.00836398,\n 0.006954616,\n + \ 0.015041992,\n 0.013283581,\n 0.030584505,\n 0.012038862,\n + \ 0.013250651,\n 0.00910159,\n -0.014686358,\n -0.0136589715,\n + \ -0.012131063,\n -0.0004058506,\n -0.006467266,\n -0.032191444,\n + \ -0.002174964,\n 0.012032276,\n 0.0037539122,\n 0.018097809,\n + \ 0.012440597,\n 0.014831246,\n -0.01682016,\n -0.0038131843,\n + \ 0.020521386,\n -0.012763301,\n -0.04167501,\n 0.03403547,\n + \ 0.00031694214,\n 0.0017995728,\n 0.024670446,\n -0.0097667575,\n + \ 0.019388627,\n 0.006124804,\n -0.0011335827,\n 0.0049624084,\n + \ 0.023366457,\n -0.005713191,\n 0.013566771,\n -0.005877836,\n + \ -0.009404538,\n -0.02737063,\n -0.011525169,\n 0.0073826937,\n + \ 0.004995337,\n 0.03029473,\n -0.011070748,\n 0.07418257,\n + \ -0.007692227,\n 0.0063849436,\n 0.0136458,\n 0.01925691,\n + \ 0.023603546,\n 0.018651016,\n 0.01844027,\n -0.008904017,\n + \ -0.033166144,\n -0.003098624,\n 0.013191379,\n -0.0014826306,\n + \ -0.027054511,\n -0.019362284,\n -0.00889743,\n -0.025526602,\n + \ 0.0015863571,\n -0.011149778,\n 0.009233307,\n 0.012203507,\n + \ 0.0102936225,\n 0.012005933,\n 0.0014760449,\n -0.01933594,\n + \ -0.02315571,\n 0.009760171,\n 0.013276994,\n -0.020547729,\n + \ -0.037565466,\n 0.0027182933,\n 0.0023560738,\n -0.0022013073,\n + \ -0.013039906,\n 0.0043663927,\n 0.009681142,\n -0.008449595,\n + \ -0.006875586,\n 0.01551617,\n 0.009312336,\n 0.02021844,\n + \ 0.0043005343,\n -0.023274256,\n -0.034931142,\n 0.014343896,\n + \ 0.0005124584,\n -0.030505475,\n -0.0034937726,\n -0.010985132\n + \ ]\n },\n {\n \"object\": \"embedding\",\n \"index\": + 1,\n \"embedding\": [\n 0.0043431777,\n -0.0016450193,\n + \ -0.0028377604,\n -0.019619407,\n -0.013388743,\n 0.00883657,\n + \ -0.015034579,\n -0.003389638,\n -0.0136238625,\n -0.007843844,\n + \ 0.021552611,\n 0.028109828,\n -0.007856906,\n 0.0106391525,\n + \ 0.018600555,\n 0.0112661375,\n 0.02740447,\n -0.0083598,\n + \ 0.023302944,\n 0.012800944,\n 0.0008890452,\n 0.021239119,\n + \ 0.0107240565,\n -0.03252485,\n -0.0033341236,\n -0.009666019,\n + \ 0.005038739,\n -0.023002513,\n 0.015517879,\n -0.026738299,\n + \ 0.0054175425,\n -0.0025863133,\n -0.009587646,\n -0.0116057545,\n + \ -0.00716461,\n -0.021839978,\n -0.020298641,\n -0.031349253,\n + \ 0.02680361,\n -0.011004893,\n 0.008738603,\n -0.0043235845,\n + \ -0.0019870854,\n -0.0012319277,\n -0.03861183,\n 0.02297639,\n + \ 0.020220267,\n -0.011925777,\n -0.009750923,\n 0.025288396,\n + \ 0.024295669,\n 0.00022573094,\n -0.019018546,\n -0.014054914,\n + \ -0.024844281,\n 0.021513425,\n -0.031297002,\n 0.015922807,\n + \ 0.024008302,\n -0.01009054,\n 0.018365437,\n -0.015805248,\n + \ -0.018796489,\n 0.023903804,\n -0.0026875453,\n 0.017359648,\n + \ 0.0062273983,\n -0.0030794109,\n -0.030487148,\n 0.026829734,\n + \ 0.017660078,\n 0.033569824,\n 0.009143531,\n -0.009345995,\n + \ 0.01802582,\n -0.0038598767,\n -0.0024818156,\n 0.0061620874,\n + \ -0.001910345,\n 0.0060641207,\n 0.024543852,\n -0.056846645,\n + \ -0.020925626,\n 0.009698675,\n 0.026881984,\n -0.0021764871,\n + \ -0.022009786,\n 0.0056330687,\n -0.021160744,\n -0.008830039,\n + \ 0.012938097,\n 0.023851555,\n 0.017803762,\n 0.02376012,\n + \ -0.015256636,\n 0.0078765,\n 0.00023593577,\n 0.033517573,\n + \ 0.009777048,\n -0.029285425,\n -0.0022679225,\n -0.021056248,\n + \ 0.010234225,\n -0.010854678,\n -0.003683537,\n -0.01558319,\n + \ -0.0014768436,\n 0.0010041557,\n 0.024191173,\n -0.006194743,\n + \ -0.02322457,\n 0.033099584,\n 0.00033431037,\n -0.023720933,\n + \ -0.017150654,\n -0.011592692,\n 0.001044975,\n 0.0016686945,\n + \ -0.017098404,\n -0.021304429,\n 0.009463555,\n 0.01713759,\n + \ 0.023799308,\n -0.04232149,\n 0.011285731,\n 0.003977436,\n + \ -0.020350888,\n -0.02501409,\n -0.0051562986,\n -0.002493245,\n + \ 0.025379831,\n 0.003389638,\n 0.030043032,\n -0.0147080235,\n + \ -0.012983815,\n 0.009123938,\n -0.006152291,\n 0.013545489,\n + \ -0.0065866085,\n -0.013467116,\n 0.011566567,\n 0.032550972,\n + \ 0.008314082,\n -0.0131144365,\n -0.009744393,\n 0.0060445275,\n + \ 0.013649987,\n 0.00498649,\n 0.009123938,\n -0.012964222,\n + \ 0.023773182,\n -0.0020050458,\n 0.002442629,\n -0.016824098,\n + \ 0.019723903,\n 0.01802582,\n -0.0142508475,\n 0.0126572605,\n + \ -0.010227693,\n 0.00044615538,\n 0.010018698,\n -0.0028197998,\n + \ 0.0028655175,\n -0.016837161,\n 0.026934233,\n 0.02638562,\n + \ 0.011475132,\n -0.0037586447,\n -0.0025210022,\n -0.025340645,\n + \ -0.008764728,\n 0.028292699,\n -0.022480026,\n 0.019893713,\n + \ -0.01713759,\n 0.033935566,\n 0.0063678166,\n 0.020403137,\n + \ -0.008510016,\n -0.024674473,\n -0.039343312,\n 0.018757302,\n + \ 0.0028573538,\n 0.03599939,\n 0.0042909286,\n 0.008268365,\n + \ 0.017346585,\n -0.004607687,\n 0.00883657,\n -0.016053429,\n + \ 0.011148578,\n 0.02805758,\n 0.02018108,\n -0.0005118745,\n + \ -0.67129195,\n -0.0048199473,\n 0.029990785,\n 0.007406261,\n + \ 0.010776306,\n 0.0019168761,\n 0.03456255,\n 0.010828554,\n + \ 0.010456282,\n 0.014551277,\n -0.018587494,\n 0.014342283,\n + \ -0.028083704,\n -0.017842948,\n -0.00343209,\n -0.0076152557,\n + \ -0.005211813,\n -0.0345103,\n 0.0018825879,\n 0.0137414215,\n + \ -0.0073344186,\n 0.03503279,\n 0.005766956,\n -0.000055106106,\n + \ 0.015191325,\n -0.008203054,\n -0.0025242679,\n -0.018260939,\n + \ -0.01874424,\n 0.009110876,\n -0.0076740356,\n 0.010835085,\n + \ -0.013375681,\n -0.005185689,\n 0.06113104,\n 0.0072560455,\n + \ 0.004888524,\n -0.014407594,\n 0.0017568643,\n 0.029703416,\n + \ -0.0058355327,\n -0.019123044,\n 0.0046664667,\n 0.006580077,\n + \ -0.009698675,\n -0.003582305,\n 0.016667353,\n -0.007765471,\n + \ -0.0045717657,\n -0.023054762,\n 0.01760783,\n -0.01086121,\n + \ 0.0136238625,\n 0.011527381,\n -0.01229805,\n 0.015792185,\n + \ 0.026516242,\n -0.006798869,\n -0.008777791,\n 0.019632468,\n + \ 0.010338722,\n 0.0045162514,\n 0.000035283214,\n 0.010168914,\n + \ -0.02136974,\n 0.017359648,\n -0.022793518,\n 0.018992422,\n + \ 0.029886287,\n -0.005629803,\n -0.005342435,\n 0.012023744,\n + \ -0.013584675,\n 0.0019348366,\n 0.02334213,\n 0.038010966,\n + \ 0.00023675217,\n -0.01718984,\n -0.006250257,\n 0.020089645,\n + \ 0.018757302,\n 0.00037880347,\n 0.0021062777,\n 0.00008975152,\n + \ 0.028344948,\n -0.014276972,\n -0.023067825,\n 0.004464003,\n + \ -0.015988119,\n 0.0016556324,\n 0.000013368333,\n 0.009319872,\n + \ -0.012663791,\n -0.018731179,\n 0.022480026,\n 0.00024471193,\n + \ -0.024922656,\n -0.0022042443,\n 0.029886287,\n -0.0015862394,\n + \ 0.0031414563,\n -0.00811815,\n 0.011142046,\n -0.010377908,\n + \ 0.0034778076,\n -0.00728217,\n -0.021761606,\n 0.009097814,\n + \ 0.025092464,\n -0.021644047,\n 0.005926968,\n 0.0026467259,\n + \ -0.011233482,\n -0.004411754,\n 0.000234303,\n -0.025876194,\n + \ 0.00716461,\n 0.00031124745,\n 0.0046109525,\n -0.022532275,\n + \ 0.018835675,\n -0.010057885,\n 0.008379393,\n -0.0021503628,\n + \ 0.0024834485,\n -0.0024867142,\n 0.015308885,\n -0.00076291343,\n + \ -0.036077764,\n -0.0018580962,\n 0.0026695847,\n 0.0021617922,\n + \ 0.018809551,\n -0.012585418,\n -0.0061620874,\n -0.01068487,\n + \ 0.0052967174,\n -0.012552762,\n -0.0020083115,\n 0.010515061,\n + \ -0.006380879,\n -0.010482406,\n -0.017686203,\n -0.0020360686,\n + \ 0.01927979,\n -0.021043185,\n 0.0013723462,\n -0.0066715125,\n + \ -0.0057049105,\n 0.004277867,\n 0.030722266,\n 0.00025348808,\n + \ -0.005672255,\n 0.024439353,\n 0.002364256,\n -0.028841311,\n + \ -0.01033219,\n -0.0188618,\n -0.04691938,\n -0.00012092729,\n + \ 0.02889356,\n 0.017111467,\n -0.00083924556,\n -0.012337237,\n + \ -0.0042223525,\n -0.025810884,\n -0.009261091,\n 0.017176777,\n + \ -0.00635802,\n -0.027979206,\n 0.007791595,\n -0.017660078,\n + \ -0.0069098976,\n 0.005949827,\n -0.006198008,\n 0.026738299,\n + \ 0.00097558217,\n -0.0142508475,\n -0.014642713,\n -0.010573842,\n + \ -0.008046308,\n -0.015544004,\n -0.025118588,\n 0.0035529153,\n + \ 0.0120564,\n -0.009829297,\n -0.0010621191,\n 0.031506,\n + \ -0.02668605,\n 0.0020442326,\n 0.00728217,\n 0.023381317,\n + \ -0.038350586,\n -0.021448113,\n 0.008692887,\n -0.018456873,\n + \ 0.005544899,\n 0.000034824,\n 0.013663049,\n 0.013349556,\n + \ 0.01618405,\n -0.015387258,\n 0.024374044,\n 0.0035006665,\n + \ 0.009189249,\n -0.02865844,\n -0.009626833,\n -0.031950112,\n + \ -0.0034157622,\n 0.015269698,\n -0.0030924731,\n 0.0031594168,\n + \ -0.005737566,\n 0.026346434,\n 0.0058845156,\n 0.05412971,\n + \ 0.0051758923,\n -0.008346738,\n -0.016797975,\n -0.008601451,\n + \ -0.003977436,\n 0.008588389,\n -0.008614513,\n 0.0065115006,\n + \ -0.0042713354,\n 0.020429263,\n 0.004052544,\n 0.0323681,\n + \ 0.017046155,\n -0.03223748,\n -0.030774515,\n -0.012970753,\n + \ 0.019828402,\n -0.002378951,\n -0.0031659477,\n -0.009300278,\n + \ 0.01509989,\n -0.028240452,\n 0.038951445,\n -0.01235683,\n + \ -0.01994596,\n 0.00716461,\n 0.036495753,\n 0.0026679519,\n + \ -0.019358164,\n 0.025628014,\n -0.001568279,\n -0.0037684413,\n + \ -0.004859134,\n 0.02561495,\n -0.028188203,\n 0.022101222,\n + \ -0.020755816,\n 0.015374196,\n 0.011769031,\n -0.022310218,\n + \ 0.00030900238,\n -0.0044803307,\n 0.038350586,\n 0.036521878,\n + \ -0.0005845329,\n 0.0043301154,\n 0.007987528,\n 0.00006301485,\n + \ -0.015191325,\n -0.021474237,\n 0.019253666,\n -0.0052967174,\n + \ 0.0045227828,\n 0.015112951,\n -0.011814749,\n -0.0014890895,\n + \ 0.00647558,\n -0.013160154,\n 0.030774515,\n 0.00231364,\n + \ 0.026777485,\n -0.018365437,\n -0.014159412,\n -0.007497696,\n + \ 0.0031349252,\n -0.02178773,\n -0.0044705337,\n 0.018822612,\n + \ -0.0022303686,\n -0.035790395,\n -0.0129446285,\n -0.0063416925,\n + \ -0.007915686,\n 0.001070283,\n -0.023120074,\n 0.03401394,\n + \ 0.009352527,\n -0.0008514914,\n -0.007001333,\n -0.010430157,\n + \ 0.009679082,\n -0.014172474,\n 0.011560037,\n 0.014407594,\n + \ 0.0073148254,\n -0.0072364523,\n 0.009594177,\n -0.016824098,\n + \ -0.003814159,\n 0.004137448,\n -0.011279199,\n -0.019488785,\n + \ -0.001049057,\n -0.018260939,\n -0.03252485,\n -0.023851555,\n + \ -0.0110963285,\n -0.014133288,\n 0.0036802716,\n -0.005002818,\n + \ -0.008255303,\n 0.012467858,\n 0.030643893,\n 0.00895413,\n + \ -0.0028720486,\n 0.0031006369,\n -0.018260939,\n -0.025758635,\n + \ 0.070535816,\n 0.014499029,\n -0.008914944,\n 0.019854525,\n + \ -0.018221753,\n 0.0023756854,\n -0.032629345,\n -0.035894893,\n + \ -0.0042419457,\n 0.01218049,\n 0.0028410258,\n 0.017072279,\n + \ -0.0068119313,\n 0.010168914,\n 0.020729693,\n 0.0053587626,\n + \ 0.008222647,\n -0.0023952788,\n -0.0023169057,\n 0.0004065606,\n + \ -0.016967783,\n -0.0018450341,\n -0.011246544,\n 0.021735482,\n + \ 0.00459789,\n -0.0028589864,\n -0.0013127499,\n 0.011860467,\n + \ -0.0010800797,\n -0.005081191,\n 0.010495468,\n 0.0002653257,\n + \ 0.010136258,\n 0.0043529742,\n -0.013689173,\n 0.031088008,\n + \ 0.026372558,\n 0.024674473,\n 0.012670322,\n -0.023838494,\n + \ 0.012010682,\n 0.01259848,\n -0.0065898737,\n -0.027195476,\n + \ -0.01402879,\n -0.0056820516,\n -0.02220572,\n 0.025275335,\n + \ -0.006962146,\n -0.038951445,\n 0.034066185,\n -0.00026308064,\n + \ -0.0002898173,\n -0.027613467,\n -0.0029732806,\n 0.015491755,\n + \ -0.021683233,\n -0.026463993,\n -0.0076805665,\n 0.012369893,\n + \ 0.0008531242,\n -0.03539853,\n -0.0066062016,\n -0.0067009027,\n + \ -0.02214041,\n -0.031610496,\n -0.033857193,\n -0.00859492,\n + \ -0.03861183,\n -0.009228436,\n 0.011240013,\n -0.03218523,\n + \ -0.013323432,\n -0.0075760693,\n 0.012781351,\n 0.014107163,\n + \ 0.0010547717,\n -0.017150654,\n -0.00552204,\n 0.003993764,\n + \ 0.0032720782,\n -0.022649834,\n -0.010697932,\n 0.007367074,\n + \ 0.00068739767,\n 0.013597738,\n 0.007837313,\n 0.0011723314,\n + \ -0.031088008,\n 0.020572945,\n 0.010586903,\n -0.000082863255,\n + \ 0.012644198,\n -0.0070535815,\n 0.028031455,\n -0.009718268,\n + \ 0.0035855707,\n 0.00465667,\n 0.0008784321,\n 0.000089496396,\n + \ 0.017294338,\n 0.0033471857,\n -0.0109787695,\n 0.0015233777,\n + \ 0.008496953,\n 0.015478693,\n 0.024556914,\n 0.033230208,\n + \ -0.008562264,\n -0.021030122,\n 0.025954569,\n -0.021683233,\n + \ -0.008686355,\n -0.009345995,\n -0.00011878427,\n 0.022427777,\n + \ 0.018456873,\n 0.04046666,\n 0.01068487,\n -0.007066644,\n + \ 0.0013698969,\n -0.029259302,\n -0.013467116,\n 0.007288701,\n + \ 0.012160897,\n 0.0039186566,\n 0.008928006,\n -0.022414714,\n + \ -0.01700697,\n -0.008137743,\n -0.007158079,\n 0.021212993,\n + \ -0.0005792264,\n -0.014290034,\n -0.026699113,\n -0.0046762633,\n + \ -0.00546326,\n 0.012324175,\n -0.0022646568,\n -0.02215347,\n + \ -0.02931155,\n -0.010364846,\n -0.0069752084,\n 0.017163714,\n + \ -0.022780456,\n -0.031427626,\n -0.018195627,\n 0.008542671,\n + \ 0.0020670912,\n 0.028841311,\n -0.015831372,\n -0.011226951,\n + \ -0.013885106,\n 0.0029291958,\n 0.002733263,\n -0.028762938,\n + \ -0.024008302,\n -0.0147080235,\n 0.0094178375,\n 0.03121863,\n + \ 0.026346434,\n 0.012030276,\n 0.011742908,\n 0.02310701,\n + \ -0.0044901273,\n 0.0054534636,\n -0.014512091,\n -0.0018972828,\n + \ -0.024517728,\n 0.014668837,\n 0.007099299,\n 0.008921474,\n + \ 0.011795156,\n 0.004101527,\n 0.013218935,\n 0.015008454,\n + \ -0.00435624,\n -0.008098557,\n -0.012493983,\n -0.00925456,\n + \ -0.0020523963,\n -0.0048493375,\n -0.0044084885,\n 0.0021438317,\n + \ -0.0003267588,\n 0.005652662,\n 0.03228973,\n 0.008457767,\n + \ -0.0026679519,\n 0.002530799,\n 0.019318976,\n -0.022780456,\n + \ 0.01921448,\n -0.015857497,\n 0.005949827,\n -0.016393047,\n + \ 0.0079940595,\n 0.012637667,\n 0.00883657,\n 0.0056755207,\n + \ -0.011279199,\n 0.018430747,\n -0.026046002,\n -0.014303096,\n + \ 0.007883031,\n 0.021382801,\n -0.007510758,\n -0.0105542485,\n + \ 0.009248029,\n -0.0052934517,\n 0.006047793,\n -0.018221753,\n + \ -0.0148778325,\n -0.03121863,\n -0.0126572605,\n -0.00231364,\n + \ -0.019501846,\n 0.026516242,\n -0.021500362,\n -0.029755665,\n + \ -0.012650729,\n -0.0065637496,\n 0.028109828,\n 0.0012760125,\n + \ 0.017215963,\n -0.0025536579,\n -0.021304429,\n -0.009006379,\n + \ 0.012578887,\n -0.014316158,\n 0.0041799,\n 0.030591644,\n + \ -0.0016099147,\n -0.00907169,\n 0.0004902402,\n -0.016693477,\n + \ 0.022649834,\n -0.010345253,\n -0.03116638,\n 0.0061849463,\n + \ 0.010038292,\n 0.0114359455,\n -0.01641917,\n -0.0075434134,\n + \ -0.019736966,\n 0.031845614,\n -0.0027871444,\n -0.02364256,\n + \ -0.01946266,\n -0.008908412,\n -0.006135963,\n 0.02340744,\n + \ -0.030905137,\n 0.025719449,\n 0.033177957,\n -0.023564188,\n + \ 0.0031022697,\n 0.0030696143,\n 0.0047513708,\n 0.0033031008,\n + \ -0.0107240565,\n 0.030931262,\n -0.0038402833,\n -0.02267596,\n + \ 0.013003408,\n -0.021931414,\n -0.013258121,\n 0.022075098,\n + \ 0.005473057,\n 0.013793671,\n -0.012585418,\n -0.00051963015,\n + \ -0.009267623,\n 0.0026254999,\n 0.019906774,\n -0.007987528,\n + \ 0.0009568053,\n -0.027247725,\n -0.0010400767,\n -0.0083598,\n + \ 0.022179596,\n 0.0024752847,\n 0.0002155261,\n -0.0048787273,\n + \ -0.006818462,\n -0.03670475,\n -0.008849633,\n 0.0032622814,\n + \ 0.015178262,\n -0.0026908107,\n -0.009920732,\n -0.01444678,\n + \ -0.005708176,\n 0.027953083,\n -0.002364256,\n -0.0028197998,\n + \ 0.0019707577,\n 0.021657107,\n -0.02650318,\n 0.010136258,\n + \ 0.012807475,\n 0.029468296,\n -0.015766062,\n 0.0032688126,\n + \ -0.002834495,\n -0.020559885,\n 0.0074519785,\n -0.016197113,\n + \ -0.0008727175,\n 0.006576812,\n 0.0074781026,\n 0.0019299383,\n + \ 0.013937355,\n -0.0057440973,\n 0.0018336046,\n 0.014081039,\n + \ 0.006194743,\n -0.029102555,\n -0.020194143,\n 0.025601888,\n + \ -0.009692144,\n 0.0043529742,\n -0.0071319547,\n 0.0043137874,\n + \ -0.008529609,\n 0.0018515652,\n -0.014224723,\n -0.0028736815,\n + \ -0.025980692,\n -0.001247439,\n 0.0043431777,\n 0.028736815,\n + \ 0.0009902772,\n -0.033517573,\n -0.029389923,\n 0.002494878,\n + \ -0.004784026,\n 0.013754484,\n 0.01815644,\n -0.009711737,\n + \ 0.0031349252,\n 0.016693477,\n -0.0044737994,\n -0.0038272212,\n + \ 0.009940325,\n 0.00087190105,\n 0.0040492783,\n -0.0063416925,\n + \ -0.017947447,\n -0.015530942,\n -0.0009396612,\n 0.034902167,\n + \ -0.0037227236,\n -0.009319872,\n -0.011788625,\n 0.0043888954,\n + \ -0.021173807,\n 0.011788625,\n -0.019123044,\n 0.029442172,\n + \ 0.008856163,\n -0.003481073,\n 0.0024491602,\n 0.037462357,\n + \ 0.011853936,\n 0.01588362,\n -0.002682647,\n -0.014368407,\n + \ 0.014603526,\n 0.016497543,\n 0.026594615,\n 0.0051530334,\n + \ -0.03234198,\n -0.0026597881,\n 0.0056036785,\n 0.010175444,\n + \ -0.008294489,\n 0.019475723,\n 0.0142508475,\n -0.012200084,\n + \ 0.0049146484,\n -0.0033733102,\n -0.0055416333,\n 0.01814338,\n + \ 0.0017095138,\n -0.005205282,\n 0.0045815627,\n 0.0032982025,\n + \ -0.008823508,\n 0.015060703,\n -0.0075760693,\n 0.018274002,\n + \ -0.023590311,\n 0.0038762044,\n -0.008581857,\n 0.004464003,\n + \ 0.0061098384,\n -0.02912868,\n 0.012023744,\n -0.0013960213,\n + \ 0.0033406545,\n 0.03874245,\n 0.0020801534,\n -0.0063416925,\n + \ 0.021330554,\n -0.025706386,\n -0.020729693,\n 0.0020801534,\n + \ 0.002703873,\n -0.017098404,\n 0.0031120663,\n -0.002689178,\n + \ -0.00071719574,\n -0.040257663,\n 0.01540032,\n -0.025719449,\n + \ -0.016863285,\n -0.012500514,\n 0.015256636,\n 0.03466705,\n + \ -0.012036806,\n -0.0020915829,\n -0.017346585,\n 0.021892227,\n + \ 0.002782246,\n -0.02417811,\n 0.013036064,\n -0.031532124,\n + \ -0.01802582,\n 0.017856011,\n -0.0073278877,\n -0.013362618,\n + \ -0.0059890132,\n 0.0041864314,\n 0.02262371,\n 0.03038265,\n + \ 0.22383365,\n -0.021644047,\n -0.007367074,\n 0.023786245,\n + \ 0.005737566,\n 0.021160744,\n -0.0046729976,\n -0.00079924264,\n + \ 0.009496211,\n 0.012493983,\n -0.023720933,\n 0.01897936,\n + \ -0.009581115,\n -0.0017584971,\n 0.0023903805,\n -0.0126899155,\n + \ -0.048617464,\n -0.024909593,\n -0.02345969,\n -0.019109981,\n + \ -0.008967192,\n -0.017555581,\n -0.017947447,\n 0.0074715717,\n + \ 0.026228873,\n -0.01152085,\n -0.0018989156,\n -0.014094101,\n + \ 0.019201417,\n 0.01737271,\n -0.0053195762,\n -0.032629345,\n + \ 0.0025128385,\n 0.01695472,\n -0.0038337521,\n -0.015779123,\n + \ 0.013454054,\n -0.008340207,\n 0.030304277,\n 0.031192506,\n + \ -0.019423474,\n 0.012324175,\n 0.0018515652,\n -0.0057636905,\n + \ 0.0022352668,\n 0.014420656,\n 0.006596405,\n -0.015792185,\n + \ 0.0019723903,\n 0.018600555,\n -0.021931414,\n -0.0016523668,\n + \ 0.0219706,\n 0.043131344,\n -0.0018319719,\n 0.01869199,\n + \ -0.008144274,\n 0.012017213,\n -0.02483122,\n -0.00006847444,\n + \ -0.016406108,\n 0.031741116,\n -0.0037096615,\n 0.0069948016,\n + \ -0.023655623,\n 0.02339438,\n -0.02167017,\n -0.0018221752,\n + \ 0.032498725,\n -0.012500514,\n 0.0013290776,\n -0.0023871148,\n + \ -0.015961993,\n 0.013819795,\n -0.033282455,\n -0.018378498,\n + \ -0.0008686355,\n 0.0071711414,\n 0.028031455,\n 0.023146197,\n + \ -0.0059661544,\n 0.012938097,\n 0.012892379,\n -0.018848738,\n + \ -0.022035912,\n -0.035555277,\n 0.025510453,\n -0.011808218,\n + \ -0.008562264,\n -0.0023120074,\n 0.013676111,\n -0.031349253,\n + \ -0.010273411,\n 0.014799459,\n 0.010417095,\n 0.029259302,\n + \ 0.00049268943,\n 0.0047317776,\n -0.018313188,\n -0.0017846215,\n + \ -0.0009600708,\n -0.041825127,\n 0.008830039,\n -0.0014809256,\n + \ -0.012683385,\n -0.01600118,\n -0.01134451,\n 0.019423474,\n + \ -0.0050550667,\n -0.022009786,\n -0.007856906,\n -0.018469933,\n + \ -0.00048126,\n -0.0074389162,\n -0.00276102,\n 0.015726874,\n + \ -0.004153776,\n 0.0054893848,\n 0.0016311407,\n -0.019541033,\n + \ 0.0199329,\n -0.01688941,\n 0.01277482,\n -0.013832857,\n + \ -0.0116057545,\n -0.0057930807,\n -0.0049505695,\n 0.003048388,\n + \ 0.013094843,\n -0.03850733,\n -0.0009959919,\n -0.004483596,\n + \ 0.030722266,\n -0.0027691838,\n -0.010404033,\n -0.004464003,\n + \ 0.01122042,\n -0.021526486,\n -0.011305324,\n 0.016066492,\n + \ -0.015230511,\n 0.0063123023,\n 0.002501409,\n 0.00011898837,\n + \ -0.018809551,\n 0.0057571596,\n 0.024909593,\n -0.0064690486,\n + \ -0.02417811,\n -0.004578297,\n -0.025144713,\n -0.008647169,\n + \ -0.026111314,\n 0.0013356088,\n 0.026751362,\n -0.013989603,\n + \ -0.032995086,\n -0.02536677,\n 0.0051171123,\n 0.012422141,\n + \ -0.040806275,\n 0.008797384,\n 0.026516242,\n -0.0074781026,\n + \ -0.038716327,\n -0.013649987,\n -0.16813649,\n 0.022231843,\n + \ 0.0060249344,\n -0.006501704,\n 0.051491145,\n 0.0054567293,\n + \ 0.021017062,\n 0.0053359037,\n -0.0022222048,\n -0.013323432,\n + \ 0.016915534,\n 0.0060673864,\n -0.019671656,\n -0.009091283,\n + \ 0.016510606,\n -0.01844381,\n -0.009182719,\n 0.022231843,\n + \ 0.021800792,\n 0.019919837,\n 0.008862695,\n -0.0007947525,\n + \ 0.0048134164,\n -0.0021797526,\n 0.008124681,\n 0.0029602183,\n + \ 0.01665429,\n 0.03466705,\n -0.020990936,\n 0.010338722,\n + \ -0.027613467,\n -0.033099584,\n 0.0398658,\n 0.0110114245,\n + \ 0.02369481,\n 0.0018564635,\n -0.015988119,\n -0.02740447,\n + \ -0.012435203,\n 0.0053816214,\n 0.02387768,\n 0.0256672,\n + \ -0.001568279,\n 0.015844434,\n -0.015060703,\n 0.003474542,\n + \ 0.0049342415,\n 0.0129446285,\n 0.00298471,\n -0.00081557035,\n + \ 0.018104192,\n -0.0030549192,\n -0.016575918,\n -0.007079706,\n + \ 0.0035006665,\n 0.011240013,\n 0.019123044,\n 0.012004151,\n + \ 0.0018401358,\n -0.0030255294,\n -0.02847557,\n -0.014642713,\n + \ 0.004823213,\n 0.014015728,\n 0.008039777,\n -0.0052803895,\n + \ -0.0059237024,\n 0.0219706,\n -0.00513344,\n 0.01665429,\n + \ 0.014472905,\n -0.02262371,\n 0.008588389,\n -0.015753,\n + \ 0.016157927,\n 0.01570075,\n -0.02894581,\n 0.018796489,\n + \ 0.01772539,\n -0.018992422,\n -0.026176626,\n 0.02466141,\n + \ -0.009796641,\n 0.00040207047,\n 0.005868188,\n 0.0065115006,\n + \ -0.0046664667,\n -0.0024312,\n -0.022662897,\n 0.006733558,\n + \ 0.03176724,\n -0.03563365,\n -0.008830039,\n -0.0041047926,\n + \ 0.0044705337,\n 0.022545338,\n 0.0057832836,\n 0.016928596,\n + \ -0.015557067,\n -0.0013347924,\n 0.009561522,\n -0.0040917303,\n + \ 0.0013739789,\n -0.008242241,\n 0.035555277,\n -0.008529609,\n + \ 0.025001029,\n 0.004496658,\n 0.0147080235,\n -0.014616588,\n + \ -0.007347481,\n 0.011109391,\n 0.023316005,\n 0.021278305,\n + \ 0.011527381,\n 0.0056200065,\n 0.0037161924,\n -0.012729103,\n + \ 0.0071842037,\n 0.006942553,\n 0.03503279,\n -0.013427929,\n + \ -0.018705053,\n 0.0010645683,\n -0.0039904984,\n -0.013134031,\n + \ -0.078686625,\n -0.029625043,\n 0.013832857,\n 0.02136974,\n + \ -0.012585418,\n 0.01456434,\n 0.00049840414,\n 0.011984558,\n + \ -0.011906184,\n 0.014851708,\n 0.004816682,\n -0.014851708,\n + \ -0.02585007,\n -0.00871248,\n 0.026307248,\n -0.023498876,\n + \ 0.0051791575,\n -0.02113462,\n -0.012611543,\n 0.032132983,\n + \ -0.0039839675,\n -0.0049538347,\n -0.0015486857,\n 0.016928596,\n + \ -0.0081638675,\n -0.004846072,\n -0.01688941,\n 0.042034123,\n + \ -0.0020834191,\n -0.00041696953,\n -0.011279199,\n -0.0077458774,\n + \ 0.010782836,\n -0.004072137,\n 0.005646131,\n 0.00596942,\n + \ -0.03061777,\n 0.011324917,\n 0.01897936,\n -0.03874245,\n + \ 0.014903957,\n 0.016249362,\n -0.031950112,\n -0.039918046,\n + \ -0.0011911083,\n -0.018522182,\n -0.0030385915,\n 0.012023744,\n + \ -0.01068487,\n -0.019867588,\n -0.0245961,\n -0.04631852,\n + \ -0.004901586,\n 0.0004437062,\n 0.027378347,\n 0.0062894435,\n + \ 0.023355192,\n 0.020207206,\n -0.011396759,\n 0.03205461,\n + \ 0.0072364523,\n -0.008203054,\n -0.02991241,\n 0.030748392,\n + \ -0.009940325,\n 0.004764433,\n -0.01295769,\n 0.006942553,\n + \ 0.01988065,\n 0.0091304695,\n -0.014786397,\n 0.028031455,\n + \ -0.0022515948,\n 0.01271604,\n -0.030931262,\n -0.01588362,\n + \ -0.013872044,\n -0.01611874,\n 0.014198598,\n -0.008784321,\n + \ -0.022048974,\n -0.026777485,\n 0.0018074802,\n 0.010214631,\n + \ 0.01839156,\n -0.00037063958,\n 0.016667353,\n -0.0030892075,\n + \ -0.008222647,\n -0.01820869,\n 0.0033341236,\n 0.010756712,\n + \ 0.013663049,\n -0.025562702,\n -0.00025022254,\n 0.010815492,\n + \ -0.010795899,\n -0.0023316005,\n 0.022336341,\n 0.0004245211,\n + \ -0.010828554,\n -0.0013609168,\n -0.049348947,\n 0.03158437,\n + \ -0.014760273,\n -0.022466963,\n 0.022780456,\n -0.01444678,\n + \ -0.004846072,\n -0.008934537,\n -0.03187174,\n -0.003582305,\n + \ -0.008862695,\n 0.0007853641,\n -0.003409231,\n -0.0029096024,\n + \ -0.04409795,\n -0.010697932,\n 0.01611874,\n 0.0052444683,\n + \ 0.016066492,\n 0.0122327395,\n 0.008575327,\n -0.0058061425,\n + \ 0.015126014,\n 0.0011886591,\n -0.031061884,\n -0.0032949368,\n + \ -0.03707049,\n -0.00078699685,\n 0.012624605,\n -0.0335437,\n + \ 0.03223748,\n -0.04284398,\n -0.012931567,\n 0.011311855,\n + \ 0.011494726,\n 0.014211661,\n -0.021957539,\n 0.009241498,\n + \ 0.00984889,\n 0.011906184,\n -0.0397613,\n -0.028162077,\n + \ 0.027430596,\n -0.0044672685,\n 0.0131144365,\n 0.008111618,\n + \ -0.028449446,\n -0.002442629,\n 0.006942553,\n 0.013715298,\n + \ 0.0016515504,\n 0.03427518,\n -0.005228141,\n -0.00468606,\n + \ -0.005649396,\n -0.0042843977,\n 0.01742496,\n 0.012990346,\n + \ -0.005348966,\n -0.017398834,\n 0.015792185,\n 0.0036312884,\n + \ 0.014773334,\n 0.0032263605,\n -0.0030157326,\n 0.014381469,\n + \ -0.037801974,\n -0.024517728,\n -0.029572794,\n -0.028971933,\n + \ -0.011279199,\n 0.015073765,\n 0.025405956,\n 0.021147683,\n + \ 0.02471366,\n 0.011749438,\n -0.015857497,\n 0.021043185,\n + \ -0.052274875,\n 0.020925626,\n 0.023538062,\n 0.0006498439,\n + \ -0.040884648,\n 0.00083924556,\n 0.012409079,\n 0.005587351,\n + \ 0.0019560626,\n -0.0037194581,\n 0.003474542,\n 0.016301611,\n + \ -0.035241783,\n -0.0031349252,\n 0.021931414,\n 0.020481512,\n + \ -0.020520698,\n 0.0119780265,\n 0.015622377,\n 0.005672255,\n + \ 0.0074781026,\n 0.009574584,\n 0.0013413235,\n 0.015256636,\n + \ -0.020729693,\n 0.00016439988,\n -0.009039034,\n 0.026960356,\n + \ -0.0214873,\n -0.022179596,\n -0.008732073,\n 0.009123938,\n + \ 0.014629651,\n 0.030513272,\n 0.023420503,\n 0.01295116,\n + \ -0.020886438,\n 0.009959918,\n 0.0087908525,\n 0.001644203,\n + \ -0.03098351,\n 0.049923684,\n -0.0021536283,\n 0.0052967174,\n + \ 0.027064854,\n 0.009744393,\n 0.023616437,\n -0.012271926,\n + \ -0.039003693,\n -0.0038794698,\n 0.026986482,\n -0.020194143,\n + \ 0.013179747,\n -0.0022009788,\n -0.019149167,\n -0.018274002,\n + \ -0.010175444,\n -0.015152139,\n 0.006531094,\n 0.04556091,\n + \ -0.010077478,\n 0.05611516,\n 0.02011577,\n -0.0068315244,\n + \ 0.020873377,\n 0.010874271,\n 0.015008454,\n -0.0057277693,\n + \ 0.018496059,\n -0.0145904645,\n -0.01635386,\n 0.030461023,\n + \ -0.017477207,\n 0.009646426,\n -0.022297155,\n -0.016523669,\n + \ 0.0024099736,\n -0.03419681,\n 0.0004555438,\n -0.0039611086,\n + \ 0.014747211,\n 0.027587341,\n 0.0021307694,\n 0.020742755,\n + \ 0.0030369586,\n -0.026777485,\n -0.016053429,\n 0.00094129395,\n + \ 0.013427929,\n -0.0056624585,\n -0.03929106,\n 0.011560037,\n + \ 0.009718268,\n 0.0053457003,\n -0.011455539,\n 0.024635287,\n + \ -0.0019772886,\n -0.01086121,\n -0.014642713,\n -0.012859724,\n + \ 0.010893865,\n 0.02046845,\n -0.011285731,\n -0.020246392,\n + \ -0.028632317,\n 0.0152174495,\n 0.0029079695,\n -0.025118588,\n + \ 0.000582492,\n -0.010880803\n ]\n }\n ],\n \"model\": + \"text-embedding-ada-002-v2\",\n \"usage\": {\n \"prompt_tokens\": 5,\n + \ \"total_tokens\": 5\n }\n}\n" + headers: + CF-RAY: + - 969f603d8b7ae5f0-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:21 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=YQYYwl4YN4kDl.QmieiV66Kp8B9Y2Vjmm76JeB5lRvc-1754324361-1.0.1.1-McDY8Ajx6shpEjbuQMRJxm1qd0AseW_8iW0T0n4m.7lyVnDTeBZtr1eohekrzghdUgR8YhVlclXiuwe03M_1be_XcxXz4sGZi_6pGBBZc.o; + path=/; expires=Mon, 04-Aug-25 16:49:21 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=uZ1AIc4GFX1ku08alsq.490RD4U6zDDkntG1wfAyRI4-1754324361958-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-ada-002-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '50' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-58cbfdf44b-tp8wh + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '81' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999994' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_a863a44cb2be457198627b1f564e51cd + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_0e0a71ad.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_0e0a71ad.yaml new file mode 100644 index 00000000000..916e60e7f6b --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_0e0a71ad.yaml @@ -0,0 +1,119 @@ +interactions: +- request: + body: "{\n \"model\": \"text-embedding-ada-002\",\n \"input\": [\n \"The + powerhouse of the cell is the mitochondria\"\n ],\n \"encoding_format\": \"base64\"\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '141' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": \"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\"\n + \ }\n ],\n \"model\": \"text-embedding-ada-002-v2\",\n \"usage\": {\n + \ \"prompt_tokens\": 9,\n \"total_tokens\": 9\n }\n}\n" + headers: + CF-RAY: + - 96b7b7f0faea3b2a-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:13:32 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=ao7XohH0XGtn3cw_5F0ns41jZFoU9asL8ZCFoSRogq0-1754579612-1.0.1.1-TprbdoS6EcGz.fBognj0mVqMTaUJtUGdBg9uCWjnkdWinWtk0rwu2pnHAONsP97xTtDCoJ3APQsGrwSeNVYWLjzecxcAjYn4ic0L8yAIjXE; + path=/; expires=Thu, 07-Aug-25 15:43:32 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=hr3C3FkOhacQ_i9GJwtIXPZQD0mAxoXsT5htlfxY5H0-1754579612848-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-ada-002-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '249' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-8c6dd7bb7-84ktq + x-envoy-upstream-service-time: + - '269' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999989' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_36b1791b33324eb5a68fcb3f271c0a74 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_18b134f3.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_18b134f3.yaml new file mode 100644 index 00000000000..08e85b33340 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_18b134f3.yaml @@ -0,0 +1,119 @@ +interactions: +- request: + body: "{\n \"model\": \"text-embedding-ada-002\",\n \"input\": \"Biology\",\n + \ \"encoding_format\": \"base64\"\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '92' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": \"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\"\n + \ }\n ],\n \"model\": \"text-embedding-ada-002-v2\",\n \"usage\": {\n + \ \"prompt_tokens\": 2,\n \"total_tokens\": 2\n }\n}\n" + headers: + CF-RAY: + - 96b7ed068c54d6a7-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:49:47 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=4sRf9TBZIr23wndzX7dzITOQj2K470Ypr69OPecYQPY-1754581787-1.0.1.1-F1MBBXsY3ZyCIHlNheQ9KG95KewYvzyGPXPo3I2IpKZiIh1bdlzoZVngP9a57KcnwmsHpCT.ziPe3Om3pBDdxyMDgMxtVROPfrbqndnaArk; + path=/; expires=Thu, 07-Aug-25 16:19:47 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=aWHNmwc1zuZOepHMV3lDL1Nwaqwk0K4HFrXc.sBXmfE-1754581787097-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-ada-002-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '178' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-554fbdff69-sbmfx + x-envoy-upstream-service-time: + - '377' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999999' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_a6ed6f8573da4bf58f880244c3a5ef55 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_3e916db1.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_3e916db1.yaml new file mode 100644 index 00000000000..bf5cb186a51 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_3e916db1.yaml @@ -0,0 +1,119 @@ +interactions: +- request: + body: "{\n \"model\": \"text-embedding-ada-002\",\n \"input\": \"The powerhouse + of the cell is the mitochondria\",\n \"encoding_format\": \"base64\"\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '131' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": \"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\"\n + \ }\n ],\n \"model\": \"text-embedding-ada-002-v2\",\n \"usage\": {\n + \ \"prompt_tokens\": 9,\n \"total_tokens\": 9\n }\n}\n" + headers: + CF-RAY: + - 96b7b7f4f864b49c-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:13:33 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=RAWyOHswb5nmEcdEQvVVBPPN2sf9fB99hmRUoGQB7XI-1754579613-1.0.1.1-PSIKBTOIHR0eYvRFUXyKIE6nFJkow9npk1gD3qhTHT_TeoF7iiwAjmFnLtdUVd375VTRpF48s4JgA.Zwftr.fmTkLK2IDgAObf2JxQNGQAk; + path=/; expires=Thu, 07-Aug-25 15:43:33 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=ZLWrfKGe_29bz1h73la1kOltd5YsB27vL4iw7Apzt5U-1754579613079-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-ada-002-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '41' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-769c5f49f9-zttk8 + x-envoy-upstream-service-time: + - '68' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999989' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_1fc1af57739c45858786091b2ea1738a + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_400ec0ec.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_400ec0ec.yaml new file mode 100644 index 00000000000..413e0b6d3c7 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_400ec0ec.yaml @@ -0,0 +1,119 @@ +interactions: +- request: + body: "{\n \"model\": \"text-embedding-ada-002\",\n \"input\": \"Hello, world!\",\n + \ \"encoding_format\": \"base64\"\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '98' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": \"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\"\n + \ }\n ],\n \"model\": \"text-embedding-ada-002-v2\",\n \"usage\": {\n + \ \"prompt_tokens\": 4,\n \"total_tokens\": 4\n }\n}\n" + headers: + CF-RAY: + - 96b7af26bbd52045-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:07:32 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=FgtdMTmMPBwNrwTMOZKWCsXYhHFhHf2uhv0tO8yTIyM-1754579252-1.0.1.1-mbmokcEQZWZE5OSpffTZjNKwx7SfsdXEJjc0efUqudGta_rCjQmXSnnC2ZWXyu9ZO2bToAnBWrp4SGAYtCh8TmG8PrClANU_pLq3JPPJGxQ; + path=/; expires=Thu, 07-Aug-25 15:37:32 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=coDbEHPr26OIqsu9tTno48ykDPkhwrrHtZHeK6GIuAo-1754579252710-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-ada-002-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '83' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-c5b9484b5-6skp9 + x-envoy-upstream-service-time: + - '118' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999997' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_edc403a9a079436f9fb2fe87198fb0b8 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_4daf25b8.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_4daf25b8.yaml new file mode 100644 index 00000000000..b016f0fc21d --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_4daf25b8.yaml @@ -0,0 +1,121 @@ +interactions: +- request: + body: "{\n \"model\": \"text-embedding-ada-002\",\n \"input\": [\n \"Hello, + world!\",\n \"Goodbye, world!\"\n ],\n \"encoding_format\": \"base64\"\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '131' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": \"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\"\n + \ },\n {\n \"object\": \"embedding\",\n \"index\": 1,\n \"embedding\": + \"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\"\n + \ }\n ],\n \"model\": \"text-embedding-ada-002-v2\",\n \"usage\": {\n + \ \"prompt_tokens\": 9,\n \"total_tokens\": 9\n }\n}\n" + headers: + CF-RAY: + - 96b7af908e703886-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Aug 2025 15:07:49 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=Q7EYxMsg2t1Comip9ov9d07N35izfF_kk_nzHa1DnOI-1754579269-1.0.1.1-VJK.WM.RwlruyjJ330JBHmhv.KCpgy8d01y4NK1IVeE0l_fTlso3h0lDJyElum_nnYXlKtIZO0q6q2m2QP6fBXjPA_UsehI.TO6k0zBc_tU; + path=/; expires=Thu, 07-Aug-25 15:37:49 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=dxKqnrr8CjexbIHQl6uk888W1_402Dz0AuwHVrbRuFY-1754579269338-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-ada-002-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '38' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-769c5f49f9-gzkth + x-envoy-upstream-service-time: + - '63' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999993' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_bd5a88accffd4599bf61aa752e4cef54 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_541bd4d0.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_541bd4d0.yaml new file mode 100644 index 00000000000..90db811312c --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_541bd4d0.yaml @@ -0,0 +1,486 @@ +interactions: +- request: + body: '{"model":"text-embedding-ada-002","input":["hello world"],"encoding_format":"float"}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '84' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": [\n -0.014910275,\n + \ 0.0013435053,\n -0.018532472,\n -0.031137712,\n -0.024248956,\n + \ 0.0074287946,\n -0.022958137,\n -0.00097223034,\n -0.012750129,\n + \ -0.022470787,\n 0.025763692,\n 0.010833658,\n -0.033034425,\n + \ -0.0038362348,\n 0.0058218567,\n 0.013856546,\n 0.019467657,\n + \ -0.022852764,\n 0.01836124,\n 0.011321009,\n -0.006193955,\n + \ 0.008752543,\n 0.0066417903,\n -0.008074204,\n -0.014712702,\n + \ -0.010860002,\n 0.018334897,\n -0.013606285,\n 0.01925691,\n + \ -0.039172404,\n 0.0025799915,\n -0.0047483696,\n -0.017755346,\n + \ -0.014857589,\n 0.010366066,\n -0.009325508,\n 0.00015404623,\n + \ -0.018255867,\n 0.02420944,\n -0.01974426,\n 0.009325508,\n + \ -0.0013006977,\n 0.017465571,\n -0.02175952,\n -0.03811867,\n + \ 0.005891008,\n 0.0016299882,\n -0.024459701,\n -0.0020745303,\n + \ 0.03385107,\n 0.029530775,\n -0.004587017,\n -0.02095605,\n + \ 0.008811815,\n -0.019757433,\n 0.020284297,\n -0.02818727,\n + \ 0.021509258,\n 0.02721257,\n -0.020244783,\n 0.0025618803,\n + \ 0.009351851,\n -0.019862805,\n 0.014488784,\n 0.0074353805,\n + \ 0.008172991,\n 0.009957746,\n 0.0027150004,\n -0.011288079,\n + \ 0.005219255,\n 0.020363327,\n 0.0126249995,\n -0.015476655,\n + \ -0.008416666,\n 0.019480828,\n -0.0007586031,\n -0.025552945,\n + \ -0.0056572114,\n 0.0016662101,\n 0.004037102,\n 0.03848748,\n + \ -0.033034425,\n -0.009608698,\n 0.0054826876,\n 0.017913405,\n + \ 0.007725156,\n -0.007830529,\n 0.024130411,\n -0.014040949,\n + \ -0.016477698,\n 0.0053740214,\n 0.0035991457,\n 0.008811815,\n + \ 0.0066714264,\n -0.011070748,\n 0.0060754106,\n -0.010267279,\n + \ 0.016780647,\n 0.00013161331,\n -0.030242043,\n -0.01924374,\n + \ -0.012743544,\n -0.015173708,\n -0.00958894,\n -0.012829159,\n + \ -0.00995116,\n 0.0069743735,\n 0.0036650037,\n 0.02347183,\n + \ 0.005476102,\n -0.015964005,\n 0.018558815,\n -0.008877673,\n + \ -0.031427488,\n -0.008522039,\n -0.004656168,\n -0.007474895,\n + \ -0.004913015,\n -0.016135236,\n -0.022220526,\n 0.007949074,\n + \ 0.024301643,\n 0.022088809,\n -0.017939748,\n 0.008522039,\n + \ 0.0039481935,\n -0.04833985,\n -0.010484611,\n -0.008317879,\n + \ -0.018045122,\n 0.037802555,\n 0.0064145797,\n 0.024709962,\n + \ -0.011202464,\n -0.028793165,\n 0.015542514,\n -0.021258997,\n + \ 0.018795904,\n -0.023379628,\n -0.019889148,\n -0.001585534,\n + \ 0.025711006,\n 0.01576643,\n -0.0055913534,\n -0.003622196,\n + \ -0.0023363163,\n 0.013566771,\n 0.0038526992,\n 0.0019806826,\n + \ -0.011801773,\n -0.001811098,\n 0.0056209895,\n 0.004840571,\n + \ 0.009496739,\n 0.020047208,\n 0.017188966,\n -0.003197411,\n + \ 0.02688328,\n 0.0046792184,\n -0.009957746,\n 0.00074831274,\n + \ 0.0028417774,\n 0.0063882363,\n -0.03485211,\n 0.006994131,\n + \ 0.03485211,\n 0.023537688,\n 0.009569183,\n 0.004988752,\n + \ -0.026698876,\n -0.00889743,\n 0.013369196,\n -0.036169272,\n + \ 0.016635759,\n -0.00022885692,\n 0.0051270537,\n 0.010866588,\n + \ 0.032138757,\n -0.0062861564,\n -0.015740087,\n -0.027739435,\n + \ 0.02323474,\n 0.007856872,\n 0.02787115,\n -0.0030179478,\n + \ -0.0027117075,\n 0.0072114626,\n 0.0028022625,\n 0.0071456046,\n + \ -0.00787663,\n 0.013046491,\n 0.034404274,\n 0.02266836,\n + \ 0.014567814,\n -0.68913925,\n -0.010530711,\n 0.009970917,\n + \ 0.00966797,\n 0.009700899,\n 0.017570943,\n 0.010583398,\n + \ 0.019546686,\n -0.00582515,\n 0.024025038,\n -0.0027100611,\n + \ 0.012941118,\n 0.00083310506,\n -0.013500912,\n -0.0086010685,\n + \ -0.009812858,\n 0.00792273,\n -0.021430228,\n -0.019941835,\n + \ 0.024393843,\n -0.010398995,\n 0.02633007,\n -0.02380112,\n + \ -0.0056967265,\n -0.00075695664,\n 0.005041438,\n -0.00012245492,\n + \ -0.016530385,\n -0.014831246,\n 0.04104277,\n -0.020271126,\n + \ 0.011347352,\n 0.009937989,\n 0.0008660341,\n 0.059904534,\n + \ 0.017557772,\n -0.01616158,\n 0.007883215,\n 0.010701942,\n + \ 0.039672922,\n -0.014146321,\n -0.01722848,\n 0.004349928,\n + \ -0.00962187,\n 0.001931289,\n 0.013072834,\n 0.008140062,\n + \ -0.0093716085,\n -0.006289449,\n -0.0046890974,\n 0.031848982,\n + \ -0.006470559,\n 0.018176837,\n 0.010352895,\n -0.0070863324,\n + \ -0.010267279,\n 0.021324854,\n -0.006602275,\n -0.002945504,\n + \ 0.013790688,\n -0.00323034,\n 0.018229524,\n -0.0010068058,\n + \ 0.0052719414,\n -0.014304381,\n 0.016280124,\n -0.030637192,\n + \ 0.0060029663,\n 0.0037143973,\n -0.0058943005,\n 0.00090719544,\n + \ 0.0051632756,\n -0.025552945,\n -0.012196922,\n 0.0136458,\n + \ 0.03582681,\n 0.016991392,\n -0.013777516,\n -0.0052587697,\n + \ 0.028635105,\n 0.020402841,\n 0.00800176,\n -0.019006649,\n + \ -0.0135272555,\n 0.017465571,\n -0.015911318,\n -0.03208607,\n + \ -0.0046133604,\n -0.0060358956,\n -0.00045689062,\n + \ 0.03738106,\n 0.007856872,\n -0.017610459,\n -0.02721257,\n + \ 0.027502345,\n 0.0040601525,\n -0.005844907,\n 0.0047187335,\n + \ 0.020692617,\n -0.0070402315,\n -0.0042939484,\n 0.010445096,\n + \ -0.007692227,\n 0.007652712,\n 0.038382106,\n -0.0039877086,\n + \ -0.0046429965,\n 0.025803206,\n 0.017821204,\n -0.01787389,\n + \ 0.0052818204,\n 0.0004163467,\n -0.028477045,\n 0.013856546,\n + \ 0.0065561747,\n -0.029873237,\n 0.0025487088,\n 0.021087766,\n + \ 0.024499215,\n -0.009661384,\n 0.0323495,\n -0.016978221,\n + \ 0.028108241,\n 0.0009845787,\n -0.0017551186,\n 0.015858632,\n + \ -0.007165362,\n -0.008917187,\n -0.00958894,\n -0.016596243,\n + \ 0.017333854,\n -0.004781299,\n 0.013388953,\n -0.013579941,\n + \ 0.013118935,\n -0.010603155,\n -0.0051237606,\n -0.015344939,\n + \ 0.005920644,\n 0.0069809593,\n -0.008047861,\n -0.0074683093,\n + \ -0.008298121,\n -0.0013303338,\n 0.0144097535,\n -0.026198355,\n + \ -0.023076681,\n -0.005291699,\n 0.005324628,\n 0.00013346557,\n + \ 0.003029473,\n -0.010221179,\n -0.03735472,\n 0.027080854,\n + \ -0.006651669,\n -0.0048866714,\n 0.0065627606,\n -0.024459701,\n + \ -0.01494979,\n -0.029135628,\n 0.004774713,\n 0.013540427,\n + \ -0.015542514,\n 0.009997261,\n -0.0045376234,\n -0.032138757,\n + \ -0.023669403,\n 0.020442357,\n 0.001141815,\n -0.038540166,\n + \ 0.003823063,\n 0.0031793,\n -0.011630542,\n 0.004972287,\n + \ -0.002701829,\n 0.022786906,\n -0.013540427,\n -0.0044026147,\n + \ 0.004152354,\n -0.0135272555,\n 0.015964005,\n -0.0069480306,\n + \ -0.019836461,\n 0.013415297,\n 0.01023435,\n 0.001331157,\n + \ 0.0023412558,\n 0.031164056,\n -0.014897104,\n 0.0022342363,\n + \ 0.0120783765,\n 0.0055024447,\n -0.012750129,\n -0.0019955006,\n + \ 0.0026491424,\n -0.011393453,\n 0.009720657,\n -0.0021831964,\n + \ 0.017860718,\n 0.013790688,\n 0.05026291,\n 0.0065825176,\n + \ 0.028898537,\n -0.02582955,\n -0.0042676055,\n -0.024630932,\n + \ -0.0060391882,\n -0.01892762,\n 0.0175446,\n 0.014199008,\n + \ 0.003444379,\n -0.018651016,\n 0.00515669,\n 0.016833331,\n + \ 0.009457224,\n 0.033561293,\n -0.0037506192,\n -0.0046594613,\n + \ -0.018321725,\n 0.003763791,\n 0.008633998,\n 0.006882172,\n + \ -0.010089462,\n -0.008146648,\n 0.015740087,\n 0.02818727,\n + \ 0.008495696,\n 0.044309333,\n 0.008673512,\n -0.01015532,\n + \ -0.028608762,\n 0.0064903167,\n 0.014396583,\n 0.004870207,\n + \ -0.002127217,\n 0.007988589,\n 0.011037819,\n -0.030321073,\n + \ 0.030321073,\n -0.0052587697,\n -0.0055320812,\n 0.0074353805,\n + \ 0.032296818,\n -0.025408057,\n -0.0054102438,\n 0.03126943,\n + \ 0.018163666,\n -0.008765714,\n 0.001720543,\n 0.042597026,\n + \ -0.012052034,\n -0.0021453279,\n -0.017478742,\n 0.008792058,\n + \ 0.009411124,\n -0.034799423,\n 0.0032500976,\n -0.0026507888,\n + \ 0.036248304,\n 0.0254344,\n 0.023919664,\n 0.008054446,\n + \ 0.0035793881,\n -0.017162623,\n -0.0006939798,\n -0.021140452,\n + \ 0.0116503,\n -0.008153234,\n -0.00082734245,\n 0.001247188,\n + \ -0.008522039,\n 0.0009343619,\n 0.015832288,\n -0.023564031,\n + \ 0.011485654,\n -0.0038889213,\n 0.0012035569,\n 0.0034180358,\n + \ 0.008923774,\n 0.004129303,\n -0.021970265,\n -0.02282642,\n + \ 0.0016373972,\n 0.0077053984,\n 0.00086109474,\n -0.03029473,\n + \ -0.03962024,\n 0.002472972,\n 0.000887438,\n 0.0036353676,\n + \ -0.017821204,\n 0.0027084146,\n 0.024327984,\n -0.025895407,\n + \ 0.007270735,\n 0.005390486,\n 0.026053468,\n -0.0034674294,\n + \ 0.000009685772,\n -0.012163992,\n 0.015173708,\n 0.0006594043,\n + \ -0.009167449,\n -0.024947051,\n 0.010833658,\n -0.00036757055,\n + \ -0.0104253385,\n -0.02331377,\n -0.0019115316,\n -0.000374568,\n + \ -0.021482915,\n 0.0012430717,\n -0.015608371,\n -0.010352895,\n + \ 0.009015975,\n 0.001862138,\n -0.010175077,\n 0.009812858,\n + \ 0.029952267,\n -0.009411124,\n -0.0063092066,\n -0.018018778,\n + \ -0.023195226,\n -0.01307942,\n 0.048366193,\n 0.011018061,\n + \ -0.0057856347,\n 0.004705562,\n -0.003444379,\n -0.006151147,\n + \ -0.04728612,\n -0.022312727,\n 0.0022243576,\n -0.008159819,\n + \ 0.0025503552,\n -0.007698813,\n -0.0038856284,\n 0.018097809,\n + \ 0.025473915,\n 0.005604525,\n 0.0156874,\n -0.022655189,\n + \ -0.008917187,\n 0.010655842,\n -0.0042906557,\n -0.006467266,\n + \ -0.00497558,\n 0.016978221,\n 0.020903364,\n -0.0027874445,\n + \ 0.008390323,\n 0.015371283,\n -0.0069151013,\n -0.0075670965,\n + \ 0.003908679,\n 0.0024038209,\n 0.01973109,\n 0.011986176,\n + \ 0.017175794,\n 0.031875324,\n 0.014343896,\n 0.022273213,\n + \ 0.006888758,\n -0.019480828,\n 0.007270735,\n 0.009174034,\n + \ -0.0040469808,\n -0.009964332,\n 0.010063119,\n 0.0131321065,\n + \ -0.014119978,\n 0.0127172,\n 0.0028565954,\n -0.037565466,\n + \ 0.032533906,\n -0.001680205,\n -0.0049327724,\n -0.015924491,\n + \ -0.013843374,\n 0.016385498,\n -0.020086722,\n -0.0118544595,\n + \ -0.012111306,\n -0.004662754,\n -0.000032568892,\n -0.014607328,\n + \ 0.0067636278,\n 0.00043919127,\n -0.026935967,\n -0.023629889,\n + \ -0.030400103,\n 0.019994522,\n -0.016767474,\n 0.001247188,\n + \ -0.014449269,\n -0.023669403,\n -0.014515127,\n -0.0028779993,\n + \ 0.021179967,\n 0.010504368,\n -0.014251694,\n 0.0022177717,\n + \ 0.010339723,\n 0.01197959,\n -0.009490154,\n -0.03298174,\n + \ 0.0018226231,\n -0.003149664,\n 0.009002803,\n 0.008594483,\n + \ 0.008357394,\n 0.008258606,\n -0.012368153,\n 0.023511345,\n + \ 0.013606285,\n 0.01084683,\n 0.015305424,\n -0.015397626,\n + \ 0.026053468,\n -0.00091707415,\n 0.008298121,\n -0.0055123237,\n + \ -0.007955659,\n 0.0030146549,\n -0.0024219318,\n -0.011479069,\n + \ -0.007527582,\n 0.00084956957,\n 0.0030558163,\n 0.008752543,\n + \ 0.023208397,\n 0.026264213,\n -0.022154666,\n -0.0165699,\n + \ 0.030742565,\n -0.026633019,\n 0.022931794,\n -0.007112676,\n + \ 0.0009755232,\n 0.024802163,\n 0.011656885,\n 0.03922509,\n + \ 0.003585974,\n -0.0165699,\n -0.0070204744,\n -0.032691963,\n + \ -0.004076617,\n 0.021140452,\n 0.030136669,\n 0.0064639733,\n + \ 0.005255477,\n -0.01859833,\n -0.0055386666,\n -0.013619456,\n + \ -0.015990349,\n 0.013270409,\n -0.0042379694,\n -0.018874934,\n + \ -0.021706833,\n -0.012776473,\n -0.015779603,\n 0.006259813,\n + \ -0.0033176022,\n -0.01803195,\n -0.019520342,\n -0.009549426,\n + \ 0.009351851,\n -0.004764834,\n -0.04004173,\n -0.03909337,\n + \ -0.012032276,\n -0.00031673635,\n -0.0065693464,\n 0.009325508,\n + \ -0.0019510464,\n -0.00028936405,\n -0.014844418,\n -0.01713628,\n + \ 0.0061906623,\n -0.012071791,\n 0.0030607556,\n -0.012157407,\n + \ 0.032428533,\n 0.0132638225,\n 0.034325246,\n 0.009187206,\n + \ 0.012987219,\n 0.013632628,\n -0.008344222,\n -0.013566771,\n + \ -0.00438615,\n -0.00205148,\n -0.015661057,\n 0.012420839,\n + \ 0.036617108,\n 0.026053468,\n -0.0072180484,\n 0.013764344,\n + \ -0.002695243,\n -0.0029356251,\n -0.0062729847,\n 0.009009389,\n + \ -0.022115152,\n -0.017491912,\n -0.02332694,\n 0.0029257464,\n + \ -0.002953736,\n -0.000679985,\n 0.015990349,\n 0.0058218567,\n + \ 0.01641184,\n 0.021338027,\n 0.018545642,\n -0.0037736695,\n + \ 0.0321651,\n -0.022431271,\n 0.017267996,\n 0.00040749705,\n + \ 0.010741457,\n -0.002752869,\n -0.0035069443,\n -0.004178697,\n + \ 0.0034641365,\n 0.012697443,\n 0.025131455,\n 0.020560902,\n + \ -0.010405581,\n -0.010715114,\n -0.010418752,\n 0.027133541,\n + \ -0.0026096276,\n -0.0035629235,\n 0.005456344,\n -0.01819001,\n + \ 0.005943694,\n -0.023840634,\n 0.008271778,\n -0.013033319,\n + \ 0.002571759,\n -0.0065166596,\n -0.024011865,\n 0.017333854,\n + \ -0.013896061,\n -0.029109284,\n -0.0053377994,\n 0.0045079873,\n + \ 0.044019558,\n 0.0047615413,\n 0.013270409,\n 0.011880803,\n + \ 0.007099504,\n -0.008179577,\n 0.010240936,\n -0.018545642,\n + \ -0.0061807833,\n 0.028160926,\n 0.01446244,\n -0.0042873626,\n + \ -0.010128977,\n 0.00987213,\n 0.0020975808,\n -0.014396583,\n + \ -0.02989958,\n 0.008298121,\n 0.023037165,\n -0.0065825176,\n + \ -0.023261083,\n 0.012262779,\n -0.046811942,\n 0.023366457,\n + \ -0.0042247977,\n -0.012348395,\n -0.018387584,\n -0.0038329419,\n + \ -0.02226004,\n 0.007659298,\n -0.03825039,\n 0.019375455,\n + \ 0.018703703,\n -0.016293297,\n -0.010148735,\n -0.00024141112,\n + \ -0.018071465,\n 0.0018917741,\n -0.011940075,\n 0.015160536,\n + \ -0.020165753,\n 0.006928273,\n 0.021970265,\n 0.010385823,\n + \ -0.028582418,\n -0.00074584305,\n 0.008528625,\n 0.014883933,\n + \ -0.027054511,\n -0.0051237606,\n -0.0059041795,\n 0.0107217,\n + \ 0.013869718,\n 0.0074353805,\n -0.008640584,\n -0.006365186,\n + \ -0.009878716,\n -0.004929479,\n 0.019507172,\n 0.004550795,\n + \ 0.001469459,\n -0.014976134,\n -0.010082876,\n -0.024169926,\n + \ -0.03933046,\n 0.0038033058,\n 0.002352781,\n -0.018400755,\n + \ -0.014251694,\n -0.012895018,\n -0.0034575507,\n 0.018005606,\n + \ 0.007837115,\n -0.015858632,\n 0.00913452,\n 0.030637192,\n + \ -0.0185193,\n -0.0064540943,\n -0.0033011376,\n 0.009852373,\n + \ -0.027265256,\n 0.012809402,\n -0.0059305225,\n -0.025816377,\n + \ 0.0007849463,\n -0.0185193,\n -0.009779929,\n 0.0015361403,\n + \ 0.015555685,\n 0.013685315,\n 0.015265909,\n -0.0002718705,\n + \ 0.029030254,\n 0.020639932,\n 0.003859285,\n -0.008317879,\n + \ -0.013566771,\n 0.027976524,\n -0.016108893,\n 0.00039494285,\n + \ -0.00672082,\n -0.0032797337,\n 0.0025371837,\n -0.005624282,\n + \ -0.0026935965,\n -0.0060951677,\n -0.03176995,\n -0.0152000515,\n + \ 0.0012628292,\n 0.007863458,\n -0.005232427,\n -0.0031628357,\n + \ -0.033824723,\n -0.01543714,\n -0.029109284,\n 0.010919274,\n + \ 0.011558098,\n -0.024090895,\n 0.013738001,\n 0.0025305978,\n + \ -0.00138549,\n 0.0058646644,\n -0.006187369,\n -0.005700019,\n + \ -0.02721257,\n -0.00623347,\n -0.007165362,\n -0.014396583,\n + \ -0.0024812042,\n 0.021575116,\n 0.001349268,\n -0.0051698615,\n + \ -0.018176837,\n -0.0104253385,\n -0.027528688,\n -0.020099895,\n + \ -0.014870761,\n 0.0055123237,\n 0.010589983,\n 0.01584546,\n + \ -0.0072048767,\n 0.023656232,\n 0.007520996,\n 0.009174034,\n + \ 0.009832615,\n -0.021601459,\n 0.015055164,\n -0.0020119653,\n + \ 0.012407667,\n 0.0077514993,\n -0.025895407,\n -0.0032715015,\n + \ 0.015502999,\n 0.0117227435,\n -0.010583398,\n 0.017281167,\n + \ 0.0061906623,\n -0.009529668,\n -0.012019104,\n -0.004738491,\n + \ -0.003375228,\n 0.0076066116,\n 0.023735262,\n -0.010609741,\n + \ 0.005196205,\n -0.0014373532,\n -0.00038547572,\n -0.00072649727,\n + \ -0.0002465563,\n 0.011913732,\n -0.011136606,\n 0.018901277,\n + \ -0.018637845,\n -0.011162949,\n 0.001982329,\n -0.021430228,\n + \ 0.033008084,\n -0.019375455,\n 0.007889802,\n 0.037749868,\n + \ -0.0005046378,\n -0.001618463,\n -0.0019543394,\n -0.009174034,\n + \ -0.012117892,\n 0.003116735,\n -0.002695243,\n -0.02029747,\n + \ -0.01859833,\n -0.0059634517,\n -0.00966797,\n -0.037170317,\n + \ 0.0054793945,\n -0.020824334,\n -0.0053542643,\n 0.0022046003,\n + \ 0.03150652,\n 0.013961919,\n -0.03192801,\n -0.011011476,\n + \ -0.015055164,\n 0.012170578,\n 0.0027841516,\n -0.010873173,\n + \ 0.007297078,\n -0.004004173,\n 0.00087920576,\n 0.020060379,\n + \ -0.012815988,\n -0.008443009,\n 0.0076790554,\n 0.014383411,\n + \ 0.003892214,\n 0.03266562,\n 0.21548773,\n -0.01820318,\n + \ -0.010636085,\n 0.033587635,\n 0.0116964,\n 0.01680699,\n + \ 0.0037012256,\n 0.0039119716,\n 0.0010891284,\n 0.023353284,\n + \ -0.020363327,\n 0.0064903167,\n -0.021338027,\n 0.0036024384,\n + \ 0.0032072898,\n -0.0019000064,\n -0.019928664,\n -0.025368543,\n + \ -0.036696136,\n -0.030426446,\n 0.0019329354,\n -0.022444444,\n + \ -0.03379838,\n -0.021864891,\n 0.021891234,\n -0.0040140515,\n + \ -0.015476655,\n -0.008857915,\n 0.036327332,\n 0.012335223,\n + \ -0.00200044,\n -0.016438184,\n 0.011676642,\n 0.011742501,\n + \ -0.018953964,\n -0.0069151013,\n 0.02542123,\n -0.004514573,\n + \ 0.04183307,\n 0.011558098,\n -0.010431924,\n -0.016938705,\n + \ -0.0032385725,\n -0.006579225,\n -0.005038145,\n 0.011893975,\n + \ 0.00833105,\n -0.033034425,\n 0.0243675,\n 0.02633007,\n + \ -0.012308881,\n 0.0102936225,\n 0.026777906,\n 0.031796295,\n + \ -0.0013319802,\n -0.0028565954,\n 0.0062104193,\n 0.010398995,\n + \ -0.0063520144,\n -0.00082734245,\n -0.005215962,\n 0.039910015,\n + \ 0.0010150381,\n 0.03192801,\n -0.017267996,\n 0.0027001824,\n + \ -0.01973109,\n -0.0061313896,\n 0.0054464657,\n -0.021785863,\n + \ -0.010754629,\n -0.0059535727,\n 0.006994131,\n -0.0072904923,\n + \ -0.024617761,\n -0.02381429,\n 0.01787389,\n 0.0073168357,\n + \ 0.026698876,\n 0.032454874,\n 0.004738491,\n -0.010287036,\n + \ 0.0124735255,\n -0.023629889,\n -0.039119717,\n -0.032138757,\n + \ -0.01108392,\n 0.012177164,\n -0.0058943005,\n -0.016504042,\n + \ -0.0034871867,\n -0.024538731,\n -0.0023083268,\n -0.0011525169,\n + \ 0.024709962,\n 0.026356414,\n 0.0036584178,\n 0.015568856,\n + \ -0.018545642,\n 0.015239566,\n -0.020073552,\n -0.0120783765,\n + \ 0.0023807706,\n -0.0024812042,\n 0.0030360587,\n 0.018348068,\n + \ -0.010188249,\n -0.0022572866,\n -0.0026639604,\n -0.0049163075,\n + \ -0.012025691,\n -0.015239566,\n 0.010003846,\n -0.0042215046,\n + \ -0.003826356,\n 0.0100301895,\n -0.01981012,\n -0.009793101,\n + \ 0.00456726,\n -0.0120783765,\n 0.005252184,\n -0.010985132,\n + \ -0.006710941,\n -0.0028714135,\n -0.0031002704,\n -0.0037308617,\n + \ -0.012690857,\n 0.0077976,\n 0.0071390187,\n -0.041516952,\n + \ -0.0008314586,\n -0.005499152,\n 0.0175446,\n 0.0035234087,\n + \ 0.0005750236,\n -0.0061478545,\n 0.013514084,\n 0.00014725461,\n + \ -0.023063509,\n 0.0031776538,\n -0.0004313706,\n -0.011762258,\n + \ -0.009272821,\n 0.001294935,\n -0.009937989,\n -0.007270735,\n + \ 0.038987998,\n -0.016438184,\n -0.013988262,\n 0.0015838875,\n + \ -0.030900624,\n -0.013882889,\n -0.003684761,\n -0.017926577,\n + \ 0.026553988,\n -0.018545642,\n -0.028318986,\n -0.029188313,\n + \ 0.009490154,\n -0.002887878,\n -0.014686358,\n 0.014857589,\n + \ 0.0323495,\n -0.012032276,\n -0.03832942,\n -0.022220526,\n + \ -0.1715472,\n 0.022879107,\n 0.0076395404,\n -0.02315571,\n + \ 0.024565075,\n 0.018703703,\n 0.029135628,\n 0.004972287,\n + \ -0.0020053794,\n 0.0033472383,\n -0.0016225792,\n -0.013896061,\n + \ -0.031954356,\n -0.010287036,\n -0.0009615284,\n -0.014528299,\n + \ 0.012289123,\n 0.020587245,\n 0.038935315,\n 0.026356414,\n + \ 0.02681742,\n -0.016925534,\n 0.020982392,\n -0.0068426575,\n + \ 0.0067833853,\n -0.014633671,\n 0.0062071267,\n 0.028635105,\n + \ 0.001494979,\n -0.009654799,\n -0.01884859,\n -0.017570943,\n + \ 0.02095605,\n 0.0072509777,\n 0.016543556,\n -0.0091279335,\n + \ 0.0053443853,\n -0.030847937,\n -0.0019378748,\n 0.026593504,\n + \ 0.026856937,\n 0.012684272,\n 0.010971961,\n -0.022141496,\n + \ -0.012335223,\n 0.0065594674,\n 0.020534558,\n 0.0072180484,\n + \ 0.015134193,\n 0.0066714264,\n 0.016859675,\n -0.011070748,\n + \ -0.004425665,\n 0.005495859,\n 0.020692617,\n 0.012243022,\n + \ 0.0066187396,\n 0.024907537,\n 0.0074024512,\n -0.017320681,\n + \ -0.0063750646,\n -0.021720003,\n -0.00022350595,\n -0.009088418,\n + \ -0.00018697529,\n 0.0003998822,\n -0.009924817,\n 0.0101421485,\n + \ -0.018005606,\n 0.014594156,\n -0.0054431725,\n -0.029214656,\n + \ -0.010774386,\n -0.032402188,\n 0.024196269,\n 0.004596896,\n + \ -0.030637192,\n 0.021904407,\n -0.012269366,\n -0.003447672,\n + \ -0.004277484,\n 0.022128325,\n 0.0018407342,\n 0.010385823,\n + \ -0.00925965,\n 0.008146648,\n -0.0034904797,\n 0.009556011,\n + \ -0.02721257,\n -0.014014605,\n 0.017913405,\n -0.0146731865,\n + \ -0.0014422926,\n -0.016688444,\n 0.0044388366,\n 0.011505411,\n + \ 0.0059535727,\n 0.014752216,\n -0.014752216,\n 0.0014241816,\n + \ -0.004372978,\n 0.0066648405,\n -0.0055254954,\n 0.0088645015,\n + \ 0.03103234,\n -0.0017831082,\n 0.027476002,\n 0.020007692,\n + \ 0.018334897,\n -0.029504431,\n -0.025526602,\n 0.01446244,\n + \ 0.020890191,\n 0.020903364,\n 0.01567423,\n 0.027686749,\n + \ -0.011459311,\n -0.018532472,\n 0.002607981,\n -0.0063783578,\n + \ 0.03345592,\n -0.0018374412,\n -0.034483306,\n 0.0010899517,\n + \ 0.0034740153,\n 0.005749413,\n -0.06696452,\n -0.033376887,\n + \ 0.008061033,\n 0.014159493,\n -0.008548383,\n 0.030004954,\n + \ -0.0015427262,\n 0.0025470622,\n 0.00016166107,\n 0.018255867,\n + \ 0.010583398,\n -0.026949137,\n -0.019006649,\n -0.009332093,\n + \ 0.018901277,\n -0.02347183,\n -0.011235394,\n -0.0023099731,\n + \ -0.020929707,\n 0.026079811,\n -0.007863458,\n -0.011538341,\n + \ 0.008370565,\n -0.004211626,\n -0.02584272,\n 0.0043762713,\n + \ -0.013020148,\n 0.03680151,\n 0.0140541205,\n -0.00035460474,\n + \ -0.016464528,\n -0.005314749,\n -0.006467266,\n -0.000006662988,\n + \ -0.0011862692,\n 0.0034970655,\n -0.042702395,\n 0.031005997,\n + \ 0.011847873,\n -0.029609805,\n 0.041148145,\n 0.010471439,\n + \ -0.0062729847,\n -0.044546425,\n -0.0023659526,\n 0.00031406086,\n + \ 0.002626092,\n 0.040147103,\n 0.0034180358,\n -0.020073552,\n + \ -0.019388627,\n -0.029214656,\n -0.026672533,\n 0.0050315596,\n + \ 0.024657276,\n -0.0038065987,\n 0.018795904,\n 0.03946218,\n + \ -0.01543714,\n 0.008232264,\n 0.018743217,\n 0.0017798153,\n + \ -0.02283959,\n 0.011907145,\n -0.0070270603,\n 0.00026796016,\n + \ -0.017926577,\n 0.0113275945,\n 0.023063509,\n 0.009141105,\n + \ -0.015924491,\n 0.029267343,\n -0.00938478,\n 0.006045774,\n + \ -0.028477045,\n 0.00058654876,\n -0.0045112805,\n -0.017939748,\n + \ 0.014989305,\n -0.017676316,\n -0.03790793,\n -0.017979264,\n + \ -0.0063915295,\n -0.0057527055,\n 0.027581375,\n 0.013211137,\n + \ -0.0042445553,\n 0.007415623,\n -0.0033851068,\n -0.030426446,\n + \ -0.022325898,\n 0.026619848,\n 0.0120981345,\n -0.0070402315,\n + \ 0.010319966,\n 0.0011796834,\n -0.009200377,\n 0.005367436,\n + \ 0.013632628,\n 0.01624061,\n -0.017847547,\n -0.012335223,\n + \ -0.06490975,\n 0.026619848,\n -0.013066249,\n -0.014067292,\n + \ 0.01502882,\n 0.002575052,\n 0.00759344,\n -0.020560902,\n + \ -0.006714234,\n -0.014106806,\n -0.03208607,\n -0.0031595428,\n + \ -0.008508868,\n 0.0010660781,\n -0.033587635,\n -0.005993088,\n + \ 0.02802921,\n 0.012618413,\n 0.019678403,\n 0.014594156,\n + \ 0.010326551,\n -0.029925924,\n -0.009345265,\n 0.0065825176,\n + \ -0.015924491,\n 0.014027777,\n -0.025684662,\n 0.00420504,\n + \ -0.005159983,\n -0.011716157,\n -0.0008215799,\n -0.03693323,\n + \ -0.0020975808,\n 0.02672522,\n 0.008021518,\n -0.00069562625,\n + \ 0.00084874633,\n 0.022800077,\n 0.004359807,\n 0.030663535,\n + \ -0.03922509,\n -0.017162623,\n 0.013461397,\n -0.00095082645,\n + \ 0.0036584178,\n 0.0045705526,\n -0.007837115,\n -0.0036254888,\n + \ 0.021008736,\n 0.007870044,\n -0.0013015209,\n 0.027423317,\n + \ -0.016609415,\n -0.024578245,\n -0.019191053,\n -0.017360197,\n + \ -0.0055814744,\n -0.0007623076,\n 0.00297514,\n -0.03385107,\n + \ 0.020574072,\n -0.008396909,\n 0.020416014,\n 0.001047144,\n + \ 0.0009961039,\n -0.005838321,\n -0.017518256,\n -0.008792058,\n + \ -0.0062367627,\n -0.021245826,\n -0.010069705,\n -0.010998304,\n + \ 0.0005145165,\n 0.013276994,\n 0.025447574,\n 0.01397509,\n + \ -0.016477698,\n 0.023972351,\n -0.036511734,\n 0.024090895,\n + \ 0.016108893,\n 0.016148409,\n -0.022431271,\n 0.010695357,\n + \ 0.032033384,\n 0.018348068,\n 0.00007388456,\n -0.0075670965,\n + \ -0.00418199,\n 0.010254107,\n -0.030584505,\n -0.013514084,\n + \ 0.0047088545,\n 0.021377541,\n -0.00836398,\n 0.006954616,\n + \ 0.015041992,\n 0.013283581,\n 0.030584505,\n 0.012038862,\n + \ 0.013250651,\n 0.00910159,\n -0.014686358,\n -0.0136589715,\n + \ -0.012131063,\n -0.0004058506,\n -0.006467266,\n -0.032191444,\n + \ -0.002174964,\n 0.012032276,\n 0.0037539122,\n 0.018097809,\n + \ 0.012440597,\n 0.014831246,\n -0.01682016,\n -0.0038131843,\n + \ 0.020521386,\n -0.012763301,\n -0.04167501,\n 0.03403547,\n + \ 0.00031694214,\n 0.0017995728,\n 0.024670446,\n -0.0097667575,\n + \ 0.019388627,\n 0.006124804,\n -0.0011335827,\n 0.0049624084,\n + \ 0.023366457,\n -0.005713191,\n 0.013566771,\n -0.005877836,\n + \ -0.009404538,\n -0.02737063,\n -0.011525169,\n 0.0073826937,\n + \ 0.004995337,\n 0.03029473,\n -0.011070748,\n 0.07418257,\n + \ -0.007692227,\n 0.0063849436,\n 0.0136458,\n 0.01925691,\n + \ 0.023603546,\n 0.018651016,\n 0.01844027,\n -0.008904017,\n + \ -0.033166144,\n -0.003098624,\n 0.013191379,\n -0.0014826306,\n + \ -0.027054511,\n -0.019362284,\n -0.00889743,\n -0.025526602,\n + \ 0.0015863571,\n -0.011149778,\n 0.009233307,\n 0.012203507,\n + \ 0.0102936225,\n 0.012005933,\n 0.0014760449,\n -0.01933594,\n + \ -0.02315571,\n 0.009760171,\n 0.013276994,\n -0.020547729,\n + \ -0.037565466,\n 0.0027182933,\n 0.0023560738,\n -0.0022013073,\n + \ -0.013039906,\n 0.0043663927,\n 0.009681142,\n -0.008449595,\n + \ -0.006875586,\n 0.01551617,\n 0.009312336,\n 0.02021844,\n + \ 0.0043005343,\n -0.023274256,\n -0.034931142,\n 0.014343896,\n + \ 0.0005124584,\n -0.030505475,\n -0.0034937726,\n -0.010985132\n + \ ]\n }\n ],\n \"model\": \"text-embedding-ada-002-v2\",\n \"usage\": + {\n \"prompt_tokens\": 2,\n \"total_tokens\": 2\n }\n}\n" + headers: + CF-RAY: + - 969f603a9e3407f5-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:21 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=9uNd100Fh5qokIX4zFuuTr9oCywk307CMm1UV9NDV8s-1754324361-1.0.1.1-e8X2ueZNwBqXpvums29PMHQgBW3tmlok4dQqyX9f6Ue66En909PlLaj6wF0kyFvsxJHTJJnVhRiYugflsQpbpMHbXQQA6Dj6erG4s5hqlsc; + path=/; expires=Mon, 04-Aug-25 16:49:21 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=0uZ6lomfPanomdh9s0Rg9L0dOFXKO3QgYqKImnSSZ8Y-1754324361753-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-ada-002-v2 + openai-organization: + - datadog-staging + openai-processing-ms: + - '62' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-6f97b7b7d-bs84c + x-envoy-decorator-operation: + - router.openai.svc.cluster.local:5004/* + x-envoy-upstream-service-time: + - '81' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999998' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_130f0435c14d410f9f46d636baffe1e6 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_cd7b3e2b.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_cd7b3e2b.yaml new file mode 100644 index 00000000000..209be3aa70e --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_embeddings_post_cd7b3e2b.yaml @@ -0,0 +1,92 @@ +interactions: +- request: + body: "{\n \"model\": \"gpt-3.5-turbo-instruct\",\n \"input\": \"Hello, world!\",\n + \ \"encoding_format\": \"base64\"\n}" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip,deflate + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '98' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-os + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-package-version + : - 4.104.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-retry-count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-runtime-version + : - v22.17.0 + ? !!python/object/apply:multidict._multidict.istr + - x-stainless-timeout + : - '600' + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: "{\n \"error\": {\n \"message\": \"You are not allowed to + generate embeddings from this model\",\n \"type\": \"invalid_request_error\",\n + \ \"param\": null,\n \"code\": null\n }\n}\n" + headers: + CF-RAY: + - 96b7ae3bcabeffbc-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 07 Aug 2025 15:06:55 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=GR6aThVvhO2dMyITZ7OMQ3gPWnwC5cVlnyej3BuwHSg-1754579215-1.0.1.1-F5HVWIT3q_4AMKecL7C3zEbuTFVR5eiNr9gs.ItwoJgy7U9zZkS9htzZCR2Aj6bcpxq87KLgc_S4LYl9fL6dF4HSyEcJKO3kTZiqAiIeeO4; + path=/; expires=Thu, 07-Aug-25 15:36:55 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=PgU7tVnNfGWWmBNHUf2QRKAL3yD8a7ibKzDGFoz1Ago-1754579215543-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + vary: + - Origin + x-envoy-upstream-service-time: + - '4' + x-request-id: + - req_77208fc5c6e649d88a5daea48c90a299 + status: + code: 403 + message: Forbidden +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_files_post_59c6532f.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_files_post_59c6532f.yaml new file mode 100644 index 00000000000..104de26cb8c --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_files_post_59c6532f.yaml @@ -0,0 +1,120 @@ +interactions: +- request: + body: "------formdata-undici-009869906004\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nfine-tune\r\n------formdata-undici-009869906004\r\nContent-Disposition: + form-data; name=\"file\"; filename=\"fine-tune.jsonl\"\r\nContent-Type: application/octet-stream\r\n\r\n{\"messages\":[{\"role\":\"user\",\"content\":\"What + is the meaning of life?\"},{\"role\":\"assistant\",\"content\":\"The meaning + of life is 42\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"What is + 5 + 5?\"},{\"role\":\"assistant\",\"content\":\"5 + 5 equals 10\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"What + is the capital of France?\"},{\"role\":\"assistant\",\"content\":\"The capital + of France is Paris\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"What + is the largest planet in our solar system?\"},{\"role\":\"assistant\",\"content\":\"Jupiter + is the largest planet in our solar system\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"How + many sides does a triangle have?\"},{\"role\":\"assistant\",\"content\":\"A + triangle has three sides\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"What + is the chemical symbol for gold?\"},{\"role\":\"assistant\",\"content\":\"The + chemical symbol for gold is Au\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"What + is the opposite of hot?\"},{\"role\":\"assistant\",\"content\":\"The opposite + of hot is cold\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"How many + days are in a week?\"},{\"role\":\"assistant\",\"content\":\"There are seven + days in a week\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"What color + is the sky on a clear day?\"},{\"role\":\"assistant\",\"content\":\"The sky + is blue on a clear day\"}]}\n{\"messages\":[{\"role\":\"user\",\"content\":\"What + is the square root of 16?\"},{\"role\":\"assistant\",\"content\":\"The square + root of 16 is 4\"}]}\r\n------formdata-undici-009869906004--\r\n" + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '1674' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - multipart/form-data; boundary=----formdata-undici-009869906004 + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v24.5.0 + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/files + response: + body: + string: "{\n \"object\": \"file\",\n \"id\": \"file-Eb5S6Mx5n7zWjJjMH4coFs\",\n + \ \"purpose\": \"fine-tune\",\n \"filename\": \"fine-tune.jsonl\",\n \"bytes\": + 1386,\n \"created_at\": 1755094019,\n \"expires_at\": null,\n \"status\": + \"processed\",\n \"status_details\": null\n}\n" + headers: + CF-RAY: + - 96e8c6b69b7c7d06-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Aug 2025 14:07:00 GMT + Server: + - cloudflare + Set-Cookie: + - _cfuvid=Yw5yXXbeKB5.7iGaBROZMiAtbGG0S2f2Ws8fcYVqwZI-1755094020094-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '249' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '252' + x-request-id: + - req_5df5f9797c2ee3dbd2a2add270a3662d + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_images_edits_post_51c64d82.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_images_edits_post_51c64d82.yaml new file mode 100644 index 00000000000..1eda315bc98 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_images_edits_post_51c64d82.yaml @@ -0,0 +1,137 @@ +interactions: +- request: + body: !!binary | + LS0tLS0tZm9ybWRhdGEtdW5kaWNpLTAzMjc5MjgxODAwMA0KQ29udGVudC1EaXNwb3NpdGlvbjog + Zm9ybS1kYXRhOyBuYW1lPSJpbWFnZSI7IGZpbGVuYW1lPSJpbWFnZS5wbmciDQpDb250ZW50LVR5 + cGU6IGltYWdlL3BuZw0KDQqJUE5HDQoaCgAAAA1JSERSAAABAAAAAQAIBgAAAFxyqGYAAAZXSURB + VHja7dy9j1xXHcfh77l33vySzGDJFgIpGwUqGiSCSEFHl5IWIWqoQKKg4A9IgYSUgi4NSpvCSOCS + DpoUEQUNSMhBBIPWL9g79u7OnbmHYq2IBrPLwuqs/DzWys3I/u1vzv3M3S1uuX379pBG1Fqzv397 + d+vWz/uka2KmZ9s38v0n39vdv/GFvjSyp7K5W2t+kMy6JkYan4713Q/ezd4v9lpZUR50H+2W48/6 + SR628Z5lzHvdj3e/Gr/aJ82sKZPlcjlpKQDr9aRfLlOSsY0FDX0Oh2t9XS5LbSUAR9eTktR5GztK + l1yfXc9yXDZzsA/Llf7VWsu0jo0EIJmVq/04LktTASilnWFKKSklpeuSWtuZqZZSUrokzQx1coaa + uSVJyvM/rQxUTrbUzETdp29cl5a0NQ0gAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIA + CAAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAI + ACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgA + IACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgACAAg + AIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAA + AAIACAAgAIAAAAIACAAgAIAAAAIACAAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAA + AgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIAAgA8DKabDa7 + pgbabkuGIUlKI/PUXB/GzDa7lNQ2ZhrGHHRJ6dvYUd3WDBmzudHKWSoZdjXbg5LSyDkqtSZXx9yY + tnW9Td5//89NDTSdzvL660mt8ybmGbY1X//T/VzpFs3s6NHib/n1Z5NM2thRt6n55ZV7Wb3dN7Oj + 8eHTvPHh9UzW6zYCsD3K7M3HefvzbV1vkzt3XmtqoMXir1mt+iRXm5hnOS7y7Yc388XtXtLIHcDd + xZjfvrpM1zVywe3G/OaHn0vZa+UslUz+eJwbf3kl3SdPmpho82TId76xytfeei2lNBSABw/6pgIw + n3c5OirN/AjQ12Ry0GU+9KmNBGA665LDktLISaq7ZD3pspk1cpZKyXTaZSjt7OhxSibTLvN5W9eb + XwLCS0wAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEABAAQAAAAQAEAGjPpJXHXJ2oSTnNPCVddzFP + eim1JCWppZ7vIUU1J08Uqv+LLdXnD7ssF/OenOpl9eSrmaNUm7vYaq2pjc01KfMHbW1pus7Y3Uwy + +7cvuXVzla98+UsXssxFFrm2u5a+nu+RYHVX8+T3T3P4h825rtuS5FHWmY23chENrBmzLQepZXjB + UCXdep3+8eM2zlAp6Q4OUofhTCE46+PDuu70N9Cz2Szr9TqPW9nRp+dp71tNJamUw5QcvfA13/3m + W/npOz/KOF7M6H3tz/146ePjTd77yQe5887v0p/33yrbPCpHF/K9j+U4D175KEf9Jy8Oxd5ecvVq + M+doPDjIcPfuSQROefHP5/MzRWCxWJzp9avVKqvVqrEfAbonTQ1Uk9S8+MGJ0+k08/kil8nYjdlO + x/w9R5mc99a9Jl2dXMz70W1T6n/+pCsff9zWJ9s4pg7D//UusZzxoaP7+/u5d+9eU3vyS0B4iQkA + CAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAg + AIAAAAIACAAgAIAAAAIACAAgAIAAAAIAAmAFIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACA + AAACAAgAIACAAAACAAgAIADABZlcxqFrrUmScRwvzczjWFNTa1LLZVv3v3xdJuWMH3D/zffZPf9/ + Tn0MWtvjpBuH7WULwOGzZ3V/f79cpgBsjoccHR+Ouxx1pbs8N15jOR6T+ijJcMmiNS+lfOYMF+iY + 5P7zv0/5OVRv1lpP+yFakvwjyWFLi/onhd9rb7zhry0AAAAASUVORK5CYIINCi0tLS0tLWZvcm1k + YXRhLXVuZGljaS0wMzI3OTI4MTgwMDANCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsg + bmFtZT0icHJvbXB0Ig0KDQpDaGFuZ2UgYWxsIHJlZCB0byBibHVlDQotLS0tLS1mb3JtZGF0YS11 + bmRpY2ktMDMyNzkyODE4MDAwDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9 + Im4iDQoNCjENCi0tLS0tLWZvcm1kYXRhLXVuZGljaS0wMzI3OTI4MTgwMDANCkNvbnRlbnQtRGlz + cG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ic2l6ZSINCg0KMjU2eDI1Ng0KLS0tLS0tZm9ybWRh + dGEtdW5kaWNpLTAzMjc5MjgxODAwMA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBu + YW1lPSJyZXNwb25zZV9mb3JtYXQiDQoNCnVybA0KLS0tLS0tZm9ybWRhdGEtdW5kaWNpLTAzMjc5 + MjgxODAwMC0tDQo= + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '2234' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - multipart/form-data; boundary=----formdata-undici-032792818000 + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v24.5.0 + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/images/edits + response: + body: + string: "{\n \"created\": 1755094032,\n \"data\": [\n {\n \"url\": + \"https://oaidalleapiprodscus.blob.core.windows.net/private/org-GKgUkEpTs8iJbqUCqnL6JW5H/user-DoUONI1dg5wtyS7luFo7MncN/img-iIK79iphcc5ltrW9hKTBwYik.png?st=2025-08-13T13%3A07%3A12Z&se=2025-08-13T15%3A07%3A12Z&sp=r&sv=2024-08-04&sr=b&rscd=inline&rsct=image/png&skoid=8b33a531-2df9-46a3-bc02-d4b1430a422c&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2025-08-13T01%3A04%3A31Z&ske=2025-08-14T01%3A04%3A31Z&sks=b&skv=2024-08-04&sig=JqiKbJbncQcVOBoo0AkbuUQ8Dtqo757pY9p0nOCHk4U%3D\"\n + \ }\n ]\n}" + headers: + CF-RAY: + - 96e8c6babcf9de6d-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Aug 2025 14:07:12 GMT + Server: + - cloudflare + Set-Cookie: + - _cfuvid=VAE.SnROFh9PxSaovFQToS6KWzg2wYuhwEZyfvvNu5M-1755094032470-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '11891' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '11899' + x-request-id: + - req_2ca6a51d07267478f3c3e12808959a39 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_images_variations_post_73d6504c.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_images_variations_post_73d6504c.yaml new file mode 100644 index 00000000000..cd42cf99864 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_images_variations_post_73d6504c.yaml @@ -0,0 +1,135 @@ +interactions: +- request: + body: !!binary | + LS0tLS0tZm9ybWRhdGEtdW5kaWNpLTAxODY2MjcyNzkwNQ0KQ29udGVudC1EaXNwb3NpdGlvbjog + Zm9ybS1kYXRhOyBuYW1lPSJuIg0KDQoxDQotLS0tLS1mb3JtZGF0YS11bmRpY2ktMDE4NjYyNzI3 + OTA1DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9InNpemUiDQoNCjI1Nngy + NTYNCi0tLS0tLWZvcm1kYXRhLXVuZGljaS0wMTg2NjI3Mjc5MDUNCkNvbnRlbnQtRGlzcG9zaXRp + b246IGZvcm0tZGF0YTsgbmFtZT0icmVzcG9uc2VfZm9ybWF0Ig0KDQp1cmwNCi0tLS0tLWZvcm1k + YXRhLXVuZGljaS0wMTg2NjI3Mjc5MDUNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsg + bmFtZT0iaW1hZ2UiOyBmaWxlbmFtZT0iaW1hZ2UucG5nIg0KQ29udGVudC1UeXBlOiBhcHBsaWNh + dGlvbi9vY3RldC1zdHJlYW0NCg0KiVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAG + V0lEQVR42u3cvY9cVx3H4e+5d978ksxgyRYCKRsFKhokgkhBR5eSFiFqqECioOAPSIGElIIuDUqb + wkjgkg6aFBEFDUjIQQSD1i/YO/buzp25h2KtiAazy8LqrPw81srNyP7tb879zN0tbrl9+/aQRtRa + s79/e3fr1s/7pGtipmfbN/L9J9/b3b/xhb40sqeyuVtrfpDMuiZGGp+O9d0P3s3eL/ZaWVEedB/t + luPP+kketvGeZcx73Y93vxq/2ifNrCmT5XI5aSkA6/WkXy5TkrGNBQ19DodrfV0uS20lAEfXk5LU + eRs7Spdcn13Pclw2c7APy5X+1VrLtI6NBCCZlav9OC5LUwEopZ1hSikpJaXrklrbmamWUlK6JM0M + dXKGmrklScrzP60MVE621MxE3advXJeWtDUNIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACA + AAACAAgACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAA + AAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAA + AgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAI + AAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgA + IACAAAACAAgAIACAAAACAAgAIACAAAACAAgACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAg + AIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAgAIAAAAIACAAIAPAy + mmw2u6YG2m5LhiFJSiPz1Fwfxsw2u5TUNmYaxhx0Senb2FHd1gwZs7nRylkqGXY124OS0sg5KrUm + V8fcmLZ1vU3ef//PTQ00nc7y+utJrfMm5hm2NV//0/1c6RbN7OjR4m/59WeTTNrYUbep+eWVe1m9 + 3Tezo/Hh07zx4fVM1us2ArA9yuzNx3n7821db5M7d15raqDF4q9ZrfokV5uYZzku8u2HN/PF7V7S + yB3A3cWY3766TNc1csHtxvzmh59L2WvlLJVM/nicG395Jd0nT5qYaPNkyHe+scrX3notpTQUgAcP + +qYCMJ93OToqzfwI0NdkctBlPvSpjQRgOuuSw5LSyEmqu2Q96bKZNXKWSsl02mUo7ezocUom0y7z + eVvXm18CwktMAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABAAQAEAAAAEABABoz6SVx1ydqEk5zTwl + XXcxT3optSQlqaWe7yFFNSdPFKr/iy3V5w+7LBfznpzqZfXkq5mjVJu72GqtqY3NNSnzB21tabrO + 2N1MMvu3L7l1c5WvfPlLF7LMRRa5truWvp7vkWB1V/Pk909z+IfNua7bkuRR1pmNt3IRDawZsy0H + qWV4wVAl3Xqd/vHjNs5QKekODlKH4UwhOOvjw7ru9DfQs9ks6/U6j1vZ0afnae9bTSWplMOUHL3w + Nd/95lv56Ts/yjhezOh97c/9eOnj403e+8kHufPO79Kf998q2zwqRxfyvY/lOA9e+ShH/ScvDsXe + XnL1ajPnaDw4yHD37kkETnnxz+fzM0VgsVic6fWr1Sqr1aqxHwG6J00NVJPUvPjBidPpNPP5IpfJ + 2I3ZTsf8PUeZnPfWvSZdnVzM+9FtU+p//qQrH3/c1ifbOKYOw//1LrGc8aGj+/v7uXfvXlN78ktA + eIkJAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAgAIACAAAAC + AAgAIACAAAACAAgAIACAAAACAAgAIACAAAACAAJgBSAAgAAAAgAIACAAgAAAAgAIACAAgAAAAgAI + ACAAgAAAAgAIACAAgAAAAgAIACAAwAWZXMaha61JknEcL83M41hTU2tSy2Vb9798XSbljB9w/833 + 2T3/f059DFrb46Qbh+1lC8Dhs2d1f3+/XKYAbI6HHB0fjrscdaW7PDdeYzkek/ooyXDJojUvpXzm + DBfomOT+879P+TlUb9ZaT/shWpL8I8lhS4v6J4Xfa2+84a8tAAAAAElFTkSuQmCCDQotLS0tLS1m + b3JtZGF0YS11bmRpY2ktMDE4NjYyNzI3OTA1LS0NCg== + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '2140' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - multipart/form-data; boundary=----formdata-undici-018662727905 + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - OpenAI/JS 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Arch + : - arm64 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Lang + : - js + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-OS + : - MacOS + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Package-Version + : - 5.12.2 + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Retry-Count + : - '0' + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime + : - node + ? !!python/object/apply:multidict._multidict.istr + - X-Stainless-Runtime-Version + : - v24.5.0 + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/images/variations + response: + body: + string: "{\n \"created\": 1755094041,\n \"data\": [\n {\n \"url\": + \"https://oaidalleapiprodscus.blob.core.windows.net/private/org-GKgUkEpTs8iJbqUCqnL6JW5H/user-DoUONI1dg5wtyS7luFo7MncN/img-UiSacwAdrYnBg4ozM4spKMpL.png?st=2025-08-13T13%3A07%3A21Z&se=2025-08-13T15%3A07%3A21Z&sp=r&sv=2024-08-04&sr=b&rscd=inline&rsct=image/png&skoid=d505667d-d6c1-4a0a-bac7-5c84a87759f8&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2025-08-13T14%3A07%3A21Z&ske=2025-08-14T14%3A07%3A21Z&sks=b&skv=2024-08-04&sig=jgtpzDZJjSCbI4rxm7bHYWgm47Hc/g9SRDoTlC2EmfM%3D\"\n + \ }\n ]\n}\n" + headers: + CF-RAY: + - 96e8c7074cf34b06-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Aug 2025 14:07:22 GMT + Server: + - cloudflare + Set-Cookie: + - _cfuvid=tmvmP8H7.LJ_WY01UzyMxsdnSrVVvnsiNnhmWyBOWMc-1755094042056-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '9183' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '9188' + x-request-id: + - req_d0009f700aadd5ab4fd5ef3b58d142cf + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_4b7c916f.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_4b7c916f.yaml new file mode 100644 index 00000000000..8c615e90c4d --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_4b7c916f.yaml @@ -0,0 +1,111 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"system","content":"You are a helpful + assistant"},{"role":"user","content":[{"type":"input_text","text":"Hello, OpenAI!"}]}],"temperature":0.5}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '183' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: "{\n \"id\": \"resp_6890dd9f1e9881919f41af478b79e59c0d556fcda7cbde08\",\n + \ \"object\": \"response\",\n \"created_at\": 1754324383,\n \"status\": + \"completed\",\n \"background\": false,\n \"error\": null,\n \"incomplete_details\": + null,\n \"instructions\": null,\n \"max_output_tokens\": null,\n \"max_tool_calls\": + null,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"output\": [\n {\n + \ \"id\": \"msg_6890dd9f766c81918be4034f94e9bcf70d556fcda7cbde08\",\n + \ \"type\": \"message\",\n \"status\": \"completed\",\n \"content\": + [\n {\n \"type\": \"output_text\",\n \"annotations\": + [],\n \"logprobs\": [],\n \"text\": \"Hello! How can I assist + you today?\"\n }\n ],\n \"role\": \"assistant\"\n }\n + \ ],\n \"parallel_tool_calls\": true,\n \"previous_response_id\": null,\n + \ \"prompt_cache_key\": null,\n \"reasoning\": {\n \"effort\": null,\n + \ \"summary\": null\n },\n \"safety_identifier\": null,\n \"service_tier\": + \"default\",\n \"store\": false,\n \"temperature\": 0.5,\n \"text\": {\n + \ \"format\": {\n \"type\": \"text\"\n }\n },\n \"tool_choice\": + \"auto\",\n \"tools\": [],\n \"top_logprobs\": 0,\n \"top_p\": 1.0,\n \"truncation\": + \"disabled\",\n \"usage\": {\n \"input_tokens\": 21,\n \"input_tokens_details\": + {\n \"cached_tokens\": 0\n },\n \"output_tokens\": 10,\n \"output_tokens_details\": + {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 31\n },\n + \ \"user\": null,\n \"metadata\": {}\n}" + headers: + CF-RAY: + - 969f60c23a6da6c0-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:43 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=HGzHN5K4RBUMb4sCI_iM_s6qP7OO5bpiRl6XB7ygB2s-1754324383-1.0.1.1-rLGkghC6xTPA9XFiSmoxdT8nhuTGUh91LvbqFHLS84vOHNyAKPYQIC6yhJrU6J1iC0dX84LHm_gejTo6a1Jo9nPI37oOcZfmRGGcAHwNmzc; + path=/; expires=Mon, 04-Aug-25 16:49:43 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=lAsnejWJkkLEwww_9CLd0yn7WE83vhtUTE3B87K4LwQ-1754324383666-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '536' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '558' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999960' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_798bf0f9b1bb6b72657cc9ce020feae8 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_5002fb36.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_5002fb36.yaml new file mode 100644 index 00000000000..b1dde14f935 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_5002fb36.yaml @@ -0,0 +1,218 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"user","content":[{"type":"input_text","text":"Invent + a character for a video game"}]}],"text":{"format":{"type":"json_schema","strict":false,"name":"response","schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"height":{"type":"string"}},"required":["name","age","height"]}}},"stream":true}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '369' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_6890dda43d7c819d8d641170572aeb90037c64c48ddc2142","object":"response","created_at":1754324388,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"json_schema","description":null,"name":"response","schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"height":{"type":"string"}},"required":["name","age","height"]},"strict":false}},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} + + + event: response.in_progress + + data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_6890dda43d7c819d8d641170572aeb90037c64c48ddc2142","object":"response","created_at":1754324388,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"json_schema","description":null,"name":"response","schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"height":{"type":"string"}},"required":["name","age","height"]},"strict":false}},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","type":"message","status":"in_progress","content":[],"role":"assistant"}} + + + event: response.content_part.added + + data: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"{\"","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"name","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":6,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"\":\"","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":7,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"Z","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":8,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"ara","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":9,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":" + Night","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":10,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"shade","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":11,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"\",\"","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":12,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"age","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":13,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"\":","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":14,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"28","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":15,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":",\"","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":16,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"height","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":17,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"\":\"","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":18,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"5","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":19,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"''","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":20,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"7","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":21,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"\\\"","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":22,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"delta":"\"}","logprobs":[]} + + + event: response.output_text.done + + data: {"type":"response.output_text.done","sequence_number":23,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"text":"{\"name\":\"Zara + Nightshade\",\"age\":28,\"height\":\"5''7\\\"\"}","logprobs":[]} + + + event: response.content_part.done + + data: {"type":"response.content_part.done","sequence_number":24,"item_id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"{\"name\":\"Zara + Nightshade\",\"age\":28,\"height\":\"5''7\\\"\"}"}} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","sequence_number":25,"output_index":0,"item":{"id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"{\"name\":\"Zara + Nightshade\",\"age\":28,\"height\":\"5''7\\\"\"}"}],"role":"assistant"}} + + + event: response.completed + + data: {"type":"response.completed","sequence_number":26,"response":{"id":"resp_6890dda43d7c819d8d641170572aeb90037c64c48ddc2142","object":"response","created_at":1754324388,"status":"completed","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[{"id":"msg_6890dda4a364819dbb67684abdd56281037c64c48ddc2142","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"{\"name\":\"Zara + Nightshade\",\"age\":28,\"height\":\"5''7\\\"\"}"}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"json_schema","description":null,"name":"response","schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"height":{"type":"string"}},"required":["name","age","height"]},"strict":false}},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":50,"input_tokens_details":{"cached_tokens":0},"output_tokens":20,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":70},"user":null,"metadata":{}}} + + + ' + headers: + CF-RAY: + - 969f60e08fa93b80-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:48 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=GRG9u63e_J_bGaMEvQpKMJElqQI66aqATCIPThzKmf0-1754324388-1.0.1.1-8z_xLrdl5BQEOVmh3rIAVkCIe4mcWCuyMFQ9NHWm3x8rNQ4g9_95abxCJHuB5zk7mvlBGFLK4bMlL9pHfrTNAEXC3UxMKZ_9yZ85_VHQR4s; + path=/; expires=Mon, 04-Aug-25 16:49:48 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=bODTzHu3Ro30h8erS1xbRECR1A_SHHrETAIjXjNqsic-1754324388320-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '82' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '95' + x-request-id: + - req_204d0689701d6c1e1d2490e017272296 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_71caa7be.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_71caa7be.yaml new file mode 100644 index 00000000000..c081cdbc60f --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_71caa7be.yaml @@ -0,0 +1,121 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"system","content":"You are a helpful + assistant"},{"role":"user","content":[{"type":"input_text","text":"What is the + weather in Tokyo?"}]},{"type":"function_call","call_id":"call_5FgaK8XzVdjNfZbXfMwZNaTu","name":"weather","arguments":"{\"location\":\"Tokyo\"}","id":"fc_6890dda5c0cc81a3902d41b559596c960968ebea286633f4"},{"type":"function_call_output","call_id":"call_5FgaK8XzVdjNfZbXfMwZNaTu","output":"{\"location\":\"Tokyo\",\"temperature\":72}"}],"tools":[{"type":"function","name":"weather","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]},"strict":false}],"tool_choice":"auto"}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '777' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: "{\n \"id\": \"resp_6890dda5f23881a38014ab61ebeb8cda0968ebea286633f4\",\n + \ \"object\": \"response\",\n \"created_at\": 1754324389,\n \"status\": + \"completed\",\n \"background\": false,\n \"error\": null,\n \"incomplete_details\": + null,\n \"instructions\": null,\n \"max_output_tokens\": null,\n \"max_tool_calls\": + null,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"output\": [\n {\n + \ \"id\": \"msg_6890dda643d881a3bc423f4b3bb3a5f30968ebea286633f4\",\n + \ \"type\": \"message\",\n \"status\": \"completed\",\n \"content\": + [\n {\n \"type\": \"output_text\",\n \"annotations\": + [],\n \"logprobs\": [],\n \"text\": \"The current temperature + in Tokyo is 72\\u00b0F. If you need more details about the weather, just let + me know!\"\n }\n ],\n \"role\": \"assistant\"\n }\n ],\n + \ \"parallel_tool_calls\": true,\n \"previous_response_id\": null,\n \"prompt_cache_key\": + null,\n \"reasoning\": {\n \"effort\": null,\n \"summary\": null\n + \ },\n \"safety_identifier\": null,\n \"service_tier\": \"default\",\n \"store\": + false,\n \"temperature\": 1.0,\n \"text\": {\n \"format\": {\n \"type\": + \"text\"\n }\n },\n \"tool_choice\": \"auto\",\n \"tools\": [\n {\n + \ \"type\": \"function\",\n \"description\": \"Get the weather in + a given location\",\n \"name\": \"weather\",\n \"parameters\": {\n + \ \"type\": \"object\",\n \"properties\": {\n \"location\": + {\n \"type\": \"string\",\n \"description\": \"The location + to get the weather for\"\n }\n },\n \"required\": [\n + \ \"location\"\n ]\n },\n \"strict\": false\n }\n + \ ],\n \"top_logprobs\": 0,\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n + \ \"usage\": {\n \"input_tokens\": 90,\n \"input_tokens_details\": {\n + \ \"cached_tokens\": 0\n },\n \"output_tokens\": 26,\n \"output_tokens_details\": + {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 116\n },\n + \ \"user\": null,\n \"metadata\": {}\n}" + headers: + CF-RAY: + - 969f60eced66879b-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:50 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=_jfOD5DtTDi1nIPUWOcJ0cdQisIVfZ4aoCfa0rP.mok-1754324390-1.0.1.1-D9ExK34Q_abB90xF4ee2YrESgEh3Hty.n3HsR_wIN4t4yDpGF9iui3HR5ElzeGFo3Xj51Ls.pX8pSqISX7eZQQeB6oV4rhNPI5HMbpqnfJo; + path=/; expires=Mon, 04-Aug-25 16:49:50 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=6vSTWZg.TKBT21xyjZisSVGWZbvfYJ5Rgd0.zVZWMdw-1754324390862-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '917' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '923' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999695' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_af4767aeba30427d8141a546a7e8a08a + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_9bc1aaed.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_9bc1aaed.yaml new file mode 100644 index 00000000000..36548f8c4ca --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_9bc1aaed.yaml @@ -0,0 +1,127 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"system","content":"You are a helpful + assistant"},{"role":"user","content":[{"type":"input_text","text":"What is the + weather in Tokyo?"}]},{"type":"function_call","call_id":"call_ofJIFWUReiB7SptNdB9Tj0Hx","name":"weather","arguments":"{\"location\":\"Tokyo\"}","id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463"},{"type":"function_call_output","call_id":"call_ofJIFWUReiB7SptNdB9Tj0Hx","output":"{\"location\":\"Tokyo\",\"temperature\":72}"}],"tools":[{"type":"function","name":"weather","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]},"strict":false}],"tool_choice":"auto","stream":true}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '791' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: "event: response.created\ndata: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_6890dda88a90819d9dc61e47baaac7c00e05ce72e10c4463\",\"object\":\"response\",\"created_at\":1754324392,\"status\":\"in_progress\",\"background\":false,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"output\":[],\"parallel_tool_calls\":true,\"previous_response_id\":null,\"prompt_cache_key\":null,\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"}},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get + the weather in a given location\",\"name\":\"weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"The + location to get the weather for\"}},\"required\":[\"location\"]},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}}}\n\nevent: + response.in_progress\ndata: {\"type\":\"response.in_progress\",\"sequence_number\":1,\"response\":{\"id\":\"resp_6890dda88a90819d9dc61e47baaac7c00e05ce72e10c4463\",\"object\":\"response\",\"created_at\":1754324392,\"status\":\"in_progress\",\"background\":false,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"output\":[],\"parallel_tool_calls\":true,\"previous_response_id\":null,\"prompt_cache_key\":null,\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"}},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get + the weather in a given location\",\"name\":\"weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"The + location to get the weather for\"}},\"required\":[\"location\"]},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}}}\n\nevent: + response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"sequence_number\":2,\"output_index\":0,\"item\":{\"id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"}}\n\nevent: + response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"sequence_number\":3,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"}}\n\nevent: + response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":4,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\"The\",\"logprobs\":[]}\n\nevent: + response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":5,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + current\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":6,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + temperature\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: + {\"type\":\"response.output_text.delta\",\"sequence_number\":7,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + in\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":8,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + Tokyo\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":9,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + is\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":10,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + \",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":11,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\"72\",\"logprobs\":[]}\n\nevent: + response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":12,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\"\xB0F\",\"logprobs\":[]}\n\nevent: + response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":13,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\".\",\"logprobs\":[]}\n\nevent: + response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":14,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + If\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":15,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + you\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":16,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + need\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":17,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + more\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":18,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + details\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":19,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + or\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":20,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + specific\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":21,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + forecasts\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":22,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\",\",\"logprobs\":[]}\n\nevent: + response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":23,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + feel\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":24,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + free\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":25,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + to\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":26,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\" + ask\",\"logprobs\":[]}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":27,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"delta\":\"!\",\"logprobs\":[]}\n\nevent: + response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"sequence_number\":28,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"text\":\"The + current temperature in Tokyo is 72\xB0F. If you need more details or specific + forecasts, feel free to ask!\",\"logprobs\":[]}\n\nevent: response.content_part.done\ndata: + {\"type\":\"response.content_part.done\",\"sequence_number\":29,\"item_id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"output_index\":0,\"content_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"The + current temperature in Tokyo is 72\xB0F. If you need more details or specific + forecasts, feel free to ask!\"}}\n\nevent: response.output_item.done\ndata: + {\"type\":\"response.output_item.done\",\"sequence_number\":30,\"output_index\":0,\"item\":{\"id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"The + current temperature in Tokyo is 72\xB0F. If you need more details or specific + forecasts, feel free to ask!\"}],\"role\":\"assistant\"}}\n\nevent: response.completed\ndata: + {\"type\":\"response.completed\",\"sequence_number\":31,\"response\":{\"id\":\"resp_6890dda88a90819d9dc61e47baaac7c00e05ce72e10c4463\",\"object\":\"response\",\"created_at\":1754324392,\"status\":\"completed\",\"background\":false,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-mini-2024-07-18\",\"output\":[{\"id\":\"msg_6890dda94450819d8c27ac27d90e05bc0e05ce72e10c4463\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"The + current temperature in Tokyo is 72\xB0F. If you need more details or specific + forecasts, feel free to ask!\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"previous_response_id\":null,\"prompt_cache_key\":null,\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"}},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get + the weather in a given location\",\"name\":\"weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"The + location to get the weather for\"}},\"required\":[\"location\"]},\"strict\":false}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":90,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":26,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":116},\"user\":null,\"metadata\":{}}}\n\n" + headers: + CF-RAY: + - 969f60fd1af4d739-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:52 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=t2U2930CYPIB7hssslOAgLfo8ALNoVd0S_DK1B6TJA8-1754324392-1.0.1.1-fhdboqiAZ0Kqgyw9DYtsE0HYFv6F_5d1wmU9kEdISNtNtCIwErrLH4oNLE3z23I9yU6sto4CDgmngETlk4rLnM.OJImZ0imEDZy1ELL11AY; + path=/; expires=Mon, 04-Aug-25 16:49:52 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=j.pOIOXsgBSA1dJJ9nzTDzb.01UhDr2zP0VT6fe9hek-1754324392609-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '66' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '72' + x-request-id: + - req_ec8565c29ff6c23550fb734905307311 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_a71f8f40.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_a71f8f40.yaml new file mode 100644 index 00000000000..527ccb01f04 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_a71f8f40.yaml @@ -0,0 +1,119 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"system","content":"You are a helpful + assistant"},{"role":"user","content":[{"type":"input_text","text":"What is the + weather in Tokyo?"}]}],"tools":[{"type":"function","name":"weather","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]},"strict":false}],"tool_choice":"auto"}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '466' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: "{\n \"id\": \"resp_6890dda5262081a38e828ea23bacd43f0968ebea286633f4\",\n + \ \"object\": \"response\",\n \"created_at\": 1754324389,\n \"status\": + \"completed\",\n \"background\": false,\n \"error\": null,\n \"incomplete_details\": + null,\n \"instructions\": null,\n \"max_output_tokens\": null,\n \"max_tool_calls\": + null,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"output\": [\n {\n + \ \"id\": \"fc_6890dda5c0cc81a3902d41b559596c960968ebea286633f4\",\n \"type\": + \"function_call\",\n \"status\": \"completed\",\n \"arguments\": + \"{\\\"location\\\":\\\"Tokyo\\\"}\",\n \"call_id\": \"call_5FgaK8XzVdjNfZbXfMwZNaTu\",\n + \ \"name\": \"weather\"\n }\n ],\n \"parallel_tool_calls\": true,\n + \ \"previous_response_id\": null,\n \"prompt_cache_key\": null,\n \"reasoning\": + {\n \"effort\": null,\n \"summary\": null\n },\n \"safety_identifier\": + null,\n \"service_tier\": \"default\",\n \"store\": false,\n \"temperature\": + 1.0,\n \"text\": {\n \"format\": {\n \"type\": \"text\"\n }\n + \ },\n \"tool_choice\": \"auto\",\n \"tools\": [\n {\n \"type\": + \"function\",\n \"description\": \"Get the weather in a given location\",\n + \ \"name\": \"weather\",\n \"parameters\": {\n \"type\": \"object\",\n + \ \"properties\": {\n \"location\": {\n \"type\": + \"string\",\n \"description\": \"The location to get the weather + for\"\n }\n },\n \"required\": [\n \"location\"\n + \ ]\n },\n \"strict\": false\n }\n ],\n \"top_logprobs\": + 0,\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"usage\": {\n \"input_tokens\": + 63,\n \"input_tokens_details\": {\n \"cached_tokens\": 0\n },\n + \ \"output_tokens\": 14,\n \"output_tokens_details\": {\n \"reasoning_tokens\": + 0\n },\n \"total_tokens\": 77\n },\n \"user\": null,\n \"metadata\": + {}\n}" + headers: + CF-RAY: + - 969f60e7dbf7f4e3-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:49 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=O0j8BE01sWwkrcnYaXPx7_DWF5oUoV79f7zLHwEsDMk-1754324389-1.0.1.1-30gfwA4Mbs95koe9lWxs_1bRQRpxANpyUDsEw04WbOtoVUCW0pq1bFU1gInwg6GJK2smLJ1CzcbWEHqdZd34Ry.YNxMQPgGjhZMdAZgyDKY; + path=/; expires=Mon, 04-Aug-25 16:49:49 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=8KBp.taNQYpDUWZni29izFf8KAWWplb0dHWWicDZjyU-1754324389837-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '690' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '695' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999720' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_99881424daf150f99e716b54f058b123 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_b8c7abaf.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_b8c7abaf.yaml new file mode 100644 index 00000000000..fa1536a17ea --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_b8c7abaf.yaml @@ -0,0 +1,142 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"system","content":"You are a helpful + assistant"},{"role":"user","content":[{"type":"input_text","text":"What is the + weather in Tokyo?"}]}],"tools":[{"type":"function","name":"weather","description":"Get + the weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]},"strict":false}],"tool_choice":"auto","stream":true}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '480' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_6890dda70538819d94f77b41f8b51d970e05ce72e10c4463","object":"response","created_at":1754324391,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"Get + the weather in a given location","name":"weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} + + + event: response.in_progress + + data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_6890dda70538819d94f77b41f8b51d970e05ce72e10c4463","object":"response","created_at":1754324391,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"Get + the weather in a given location","name":"weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","type":"function_call","status":"in_progress","arguments":"","call_id":"call_ofJIFWUReiB7SptNdB9Tj0Hx","name":"weather"}} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","sequence_number":3,"item_id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","output_index":0,"delta":"{\""} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","sequence_number":4,"item_id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","output_index":0,"delta":"location"} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","sequence_number":5,"item_id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","output_index":0,"delta":"\":\""} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","sequence_number":6,"item_id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","output_index":0,"delta":"Tokyo"} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","sequence_number":7,"item_id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","output_index":0,"delta":"\"}"} + + + event: response.function_call_arguments.done + + data: {"type":"response.function_call_arguments.done","sequence_number":8,"item_id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","output_index":0,"arguments":"{\"location\":\"Tokyo\"}"} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","sequence_number":9,"output_index":0,"item":{"id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","type":"function_call","status":"completed","arguments":"{\"location\":\"Tokyo\"}","call_id":"call_ofJIFWUReiB7SptNdB9Tj0Hx","name":"weather"}} + + + event: response.completed + + data: {"type":"response.completed","sequence_number":10,"response":{"id":"resp_6890dda70538819d94f77b41f8b51d970e05ce72e10c4463","object":"response","created_at":1754324391,"status":"completed","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[{"id":"fc_6890dda84358819d92119532b61decc40e05ce72e10c4463","type":"function_call","status":"completed","arguments":"{\"location\":\"Tokyo\"}","call_id":"call_ofJIFWUReiB7SptNdB9Tj0Hx","name":"weather"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"Get + the weather in a given location","name":"weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + location to get the weather for"}},"required":["location"]},"strict":false}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":63,"input_tokens_details":{"cached_tokens":0},"output_tokens":14,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":77},"user":null,"metadata":{}}} + + + ' + headers: + CF-RAY: + - 969f60f34f60541a-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:51 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=pvcshZZa8yO1uhSaF9dIW92G7dqy6hciv2nCdHB69sk-1754324391-1.0.1.1-qpEh4T0XzB5.gXTF9T2JWjWoRautepoXyntBLht357FB.7403jQ38MK7qonZpEqsZepOvxq9cRp8DndKdqYHv7yeJmP8k3KshEJRDxFxApI; + path=/; expires=Mon, 04-Aug-25 16:49:51 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=9m_pYfhbNM_2v0GDWn3CgRZf33vA2h69DaFZ_Ol31Ic-1754324391056-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '43' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '48' + x-request-id: + - req_45c8fa64c7dd82ee93c1dcb4b078522a + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_db088a4e.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_db088a4e.yaml new file mode 100644 index 00000000000..49a957bb7b2 --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_db088a4e.yaml @@ -0,0 +1,173 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"system","content":"You are a helpful + assistant"},{"role":"user","content":[{"type":"input_text","text":"Hello, OpenAI!"}]}],"temperature":0.5,"stream":true}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '197' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_6890dda2cff881a0ae62563af4cb1800083f50d3bda8ac41","object":"response","created_at":1754324386,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.5,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} + + + event: response.in_progress + + data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_6890dda2cff881a0ae62563af4cb1800083f50d3bda8ac41","object":"response","created_at":1754324386,"status":"in_progress","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.5,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}}} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","type":"message","status":"in_progress","content":[],"role":"assistant"}} + + + event: response.content_part.added + + data: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":"Hello","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":"!","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":6,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":" + How","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":7,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":" + can","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":8,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":" + I","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":9,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":" + assist","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":10,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":" + you","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":11,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":" + today","logprobs":[]} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","sequence_number":12,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"delta":"?","logprobs":[]} + + + event: response.output_text.done + + data: {"type":"response.output_text.done","sequence_number":13,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"text":"Hello! + How can I assist you today?","logprobs":[]} + + + event: response.content_part.done + + data: {"type":"response.content_part.done","sequence_number":14,"item_id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"Hello! + How can I assist you today?"}} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","sequence_number":15,"output_index":0,"item":{"id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"Hello! + How can I assist you today?"}],"role":"assistant"}} + + + event: response.completed + + data: {"type":"response.completed","sequence_number":16,"response":{"id":"resp_6890dda2cff881a0ae62563af4cb1800083f50d3bda8ac41","object":"response","created_at":1754324386,"status":"completed","background":false,"error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4o-mini-2024-07-18","output":[{"id":"msg_6890dda36fc881a0b072598b970678ab083f50d3bda8ac41","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"Hello! + How can I assist you today?"}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":0.5,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":21,"input_tokens_details":{"cached_tokens":0},"output_tokens":10,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":31},"user":null,"metadata":{}}} + + + ' + headers: + CF-RAY: + - 969f60d72a59e5fe-IAD + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: + - Mon, 04 Aug 2025 16:19:47 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=MrtIJ_OaIEs4MPN6M6irabJM1dhujKEO0QYKaMPdR0o-1754324387-1.0.1.1-huG1y6neahw_bk6y9Y9R6u1wh9MVN4R3hC7g0vsgw0wSt2FuPOMiAjrAfaneCBMv25x8GiiKlxV34_q6DPvPRRCtJQd1u6okZofj0cxd3wA; + path=/; expires=Mon, 04-Aug-25 16:49:47 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=feKlu3RlPE4UZ.eCSIXFulZTFlLB2tSy5LF_dXnTiDQ-1754324387063-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '466' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '608' + x-request-id: + - req_52e3eecd2336b010303b294fd0e9d58f + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_e13b1667.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_e13b1667.yaml new file mode 100644 index 00000000000..80ae0cc450d --- /dev/null +++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_e13b1667.yaml @@ -0,0 +1,118 @@ +interactions: +- request: + body: '{"model":"gpt-4o-mini","input":[{"role":"user","content":[{"type":"input_text","text":"Invent + a character for a video game"}]}],"text":{"format":{"type":"json_schema","strict":false,"name":"response","schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"height":{"type":"string"}},"required":["name","age","height"]}}}}' + headers: + ? !!python/object/apply:multidict._multidict.istr + - Accept + : - '*/*' + ? !!python/object/apply:multidict._multidict.istr + - Accept-Encoding + : - gzip, deflate + ? !!python/object/apply:multidict._multidict.istr + - Accept-Language + : - '*' + ? !!python/object/apply:multidict._multidict.istr + - Connection + : - keep-alive + Content-Length: + - '355' + ? !!python/object/apply:multidict._multidict.istr + - Content-Type + : - application/json + ? !!python/object/apply:multidict._multidict.istr + - User-Agent + : - node + ? !!python/object/apply:multidict._multidict.istr + - sec-fetch-mode + : - cors + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: "{\n \"id\": \"resp_6890dda009cc81a08285f7be102389890ed73be03fe4ca87\",\n + \ \"object\": \"response\",\n \"created_at\": 1754324384,\n \"status\": + \"completed\",\n \"background\": false,\n \"error\": null,\n \"incomplete_details\": + null,\n \"instructions\": null,\n \"max_output_tokens\": null,\n \"max_tool_calls\": + null,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"output\": [\n {\n + \ \"id\": \"msg_6890dda1da4c81a0a6427d70cef778700ed73be03fe4ca87\",\n + \ \"type\": \"message\",\n \"status\": \"completed\",\n \"content\": + [\n {\n \"type\": \"output_text\",\n \"annotations\": + [],\n \"logprobs\": [],\n \"text\": \"{\\\"name\\\":\\\"Zara + Windrider\\\",\\\"age\\\":28,\\\"height\\\":\\\"5'7\\\\\\\"\\\"}\"\n }\n + \ ],\n \"role\": \"assistant\"\n }\n ],\n \"parallel_tool_calls\": + true,\n \"previous_response_id\": null,\n \"prompt_cache_key\": null,\n + \ \"reasoning\": {\n \"effort\": null,\n \"summary\": null\n },\n \"safety_identifier\": + null,\n \"service_tier\": \"default\",\n \"store\": false,\n \"temperature\": + 1.0,\n \"text\": {\n \"format\": {\n \"type\": \"json_schema\",\n + \ \"description\": null,\n \"name\": \"response\",\n \"schema\": + {\n \"type\": \"object\",\n \"properties\": {\n \"name\": + {\n \"type\": \"string\"\n },\n \"age\": {\n + \ \"type\": \"number\"\n },\n \"height\": {\n + \ \"type\": \"string\"\n }\n },\n \"required\": + [\n \"name\",\n \"age\",\n \"height\"\n ]\n + \ },\n \"strict\": false\n }\n },\n \"tool_choice\": \"auto\",\n + \ \"tools\": [],\n \"top_logprobs\": 0,\n \"top_p\": 1.0,\n \"truncation\": + \"disabled\",\n \"usage\": {\n \"input_tokens\": 50,\n \"input_tokens_details\": + {\n \"cached_tokens\": 0\n },\n \"output_tokens\": 21,\n \"output_tokens_details\": + {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 71\n },\n + \ \"user\": null,\n \"metadata\": {}\n}" + headers: + CF-RAY: + - 969f60c65c8cd69b-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 04 Aug 2025 16:19:46 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=SoAoijKRaYUZK70HzVIlnH6UVC.Y7EeohrHGd6vXCmI-1754324386-1.0.1.1-HH0yfGn0wwFrshCmJivBqOT5nPDyOcyw9eH3Phwmrrc5ZpLElUdoKfAJeB1HCVDbH1.Po53nm6SJ6AqUlUaH.ttKCGtOKpTwTMitb5ZGjLc; + path=/; expires=Mon, 04-Aug-25 16:49:46 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=htFg.7nuKLc8JeCyYD3vYk.nwsfapb.i.SNcIoCgZ8I-1754324386331-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - datadog-staging + openai-processing-ms: + - '2294' + openai-project: + - proj_gt6TQZPRbZfoY2J9AQlEJMpd + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-decorator-operation: + - tasksapi.openai.svc.cluster.local:8081/* + x-envoy-upstream-service-time: + - '2552' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999930' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_5530377b7d89bbed0b33fb8a3f89a832 + status: + code: 200 + message: OK +version: 1 diff --git a/packages/dd-trace/test/llmobs/plugins/ai/index.spec.js b/packages/dd-trace/test/llmobs/plugins/ai/index.spec.js new file mode 100644 index 00000000000..91356c02526 --- /dev/null +++ b/packages/dd-trace/test/llmobs/plugins/ai/index.spec.js @@ -0,0 +1,664 @@ +'use strict' + +const { useEnv } = require('../../../../../../integration-tests/helpers') +const chai = require('chai') +const { expect } = chai +const semifies = require('semifies') +const { withVersions } = require('../../../setup/mocha') + +const { NODE_MAJOR } = require('../../../../../../version') + +const { + expectedLLMObsLLMSpanEvent, + expectedLLMObsNonLLMSpanEvent, + deepEqualWithMockValues, + MOCK_STRING, + useLlmobs, + MOCK_NUMBER, + MOCK_OBJECT +} = require('../../util') + +chai.Assertion.addMethod('deepEqualWithMockValues', deepEqualWithMockValues) + +// ai<4.0.2 is not supported in CommonJS with Node.js < 22 +const range = NODE_MAJOR < 22 ? '>=4.0.2' : '>=4.0.0' + +function getAiSdkOpenAiPackage (vercelAiVersion) { + return semifies(vercelAiVersion, '>=5.0.0') ? '@ai-sdk/openai' : '@ai-sdk/openai@1.3.23' +} + +describe('Plugin', () => { + useEnv({ + OPENAI_API_KEY: '', + _DD_LLMOBS_FLUSH_INTERVAL: 0 + }) + + withVersions('ai', 'ai', range, (version, _, realVersion) => { + let ai + let openai + + const getEvents = useLlmobs({ plugin: 'ai' }) + + beforeEach(function () { + ai = require(`../../../../../../versions/ai@${version}`).get() + + const OpenAI = require(`../../../../../../versions/${getAiSdkOpenAiPackage(realVersion)}`).get() + openai = OpenAI.createOpenAI({ + baseURL: 'http://127.0.0.1:9126/vcr/openai', + compatibility: 'strict' + }) + }) + + it('creates a span for generateText', async () => { + await ai.generateText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'Hello, OpenAI!', + maxTokens: 100, + temperature: 0.5 + }) + + const { apmSpans, llmobsSpans } = await getEvents() + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'generateText', + spanKind: 'workflow', + inputValue: 'Hello, OpenAI!', + outputValue: MOCK_STRING, + metadata: { + maxTokens: 100, + temperature: 0.5, + maxSteps: MOCK_NUMBER, + maxRetries: MOCK_NUMBER, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedLlmSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doGenerate', + inputMessages: [ + { content: 'You are a helpful assistant', role: 'system' }, + { content: 'Hello, OpenAI!', role: 'user' } + ], + outputMessages: [{ content: MOCK_STRING, role: 'assistant' }], + metadata: { + max_tokens: 100, + temperature: 0.5, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + expect(llmobsSpans[0]).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmobsSpans[1]).to.deepEqualWithMockValues(expectedLlmSpan) + }) + + it('creates a span for generateObject', async () => { + const schema = ai.jsonSchema({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + height: { type: 'string' } + }, + required: ['name', 'age', 'height'] + }) + + await ai.generateObject({ + model: openai('gpt-4o-mini'), + schema, + prompt: 'Invent a character for a video game' + }) + + const { apmSpans, llmobsSpans } = await getEvents() + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'generateObject', + spanKind: 'workflow', + inputValue: 'Invent a character for a video game', + outputValue: MOCK_STRING, + metadata: { + schema: MOCK_OBJECT, + output: 'object', + maxRetries: MOCK_NUMBER, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedLlmSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doGenerate', + inputMessages: [{ content: 'Invent a character for a video game', role: 'user' }], + outputMessages: [{ content: MOCK_STRING, role: 'assistant' }], + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + expect(llmobsSpans[0]).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmobsSpans[1]).to.deepEqualWithMockValues(expectedLlmSpan) + }) + + it('creates a span for embed', async () => { + await ai.embed({ + model: openai.embedding('text-embedding-ada-002'), + value: 'hello world' + }) + + const { apmSpans, llmobsSpans } = await getEvents() + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'embed', + spanKind: 'workflow', + inputValue: 'hello world', + outputValue: '[1 embedding(s) returned with size 1536]', + metadata: { + maxSteps: MOCK_NUMBER, + maxRetries: MOCK_NUMBER, + }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + const expectedEmbeddingSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'embedding', + modelName: 'text-embedding-ada-002', + modelProvider: 'openai', + name: 'doEmbed', + inputDocuments: [{ text: 'hello world' }], + outputValue: '[1 embedding(s) returned with size 1536]', + tokenMetrics: { input_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + expect(llmobsSpans[0]).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmobsSpans[1]).to.deepEqualWithMockValues(expectedEmbeddingSpan) + }) + + it('creates a span for embedMany', async () => { + await ai.embedMany({ + model: openai.embedding('text-embedding-ada-002'), + values: ['hello world', 'goodbye world'] + }) + + const { apmSpans, llmobsSpans } = await getEvents() + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'embedMany', + spanKind: 'workflow', + inputValue: JSON.stringify(['hello world', 'goodbye world']), + outputValue: '[2 embedding(s) returned with size 1536]', + metadata: { + maxSteps: MOCK_NUMBER, + maxRetries: MOCK_NUMBER, + }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + const expectedEmbeddingSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'embedding', + modelName: 'text-embedding-ada-002', + modelProvider: 'openai', + name: 'doEmbed', + inputDocuments: [{ text: 'hello world' }, { text: 'goodbye world' }], + outputValue: '[2 embedding(s) returned with size 1536]', + tokenMetrics: { input_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + expect(llmobsSpans[0]).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmobsSpans[1]).to.deepEqualWithMockValues(expectedEmbeddingSpan) + }) + + it('creates a span for streamText', async () => { + const result = await ai.streamText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'Hello, OpenAI!', + maxTokens: 100, + temperature: 0.5 + }) + + const textStream = result.textStream + + for await (const part of textStream) {} // eslint-disable-line + + const { apmSpans, llmobsSpans } = await getEvents() + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'streamText', + spanKind: 'workflow', + inputValue: 'Hello, OpenAI!', + outputValue: 'Hello! How can I assist you today?', // assert text from stream is fully captured + metadata: { + maxSteps: MOCK_NUMBER, + maxRetries: MOCK_NUMBER, + }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + const expectedLlmSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doStream', + inputMessages: [ + { content: 'You are a helpful assistant', role: 'system' }, + { content: 'Hello, OpenAI!', role: 'user' } + ], + metadata: { + max_tokens: 100, + temperature: 0.5, + }, + outputMessages: [{ content: 'Hello! How can I assist you today?', role: 'assistant' }], + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + expect(llmobsSpans[0]).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmobsSpans[1]).to.deepEqualWithMockValues(expectedLlmSpan) + }) + + it('creates a span for streamObject', async () => { + const schema = ai.jsonSchema({ + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + height: { type: 'string' } + }, + required: ['name', 'age', 'height'] + }) + + const result = await ai.streamObject({ + model: openai('gpt-4o-mini'), + schema, + prompt: 'Invent a character for a video game' + }) + + const partialObjectStream = result.partialObjectStream + + for await (const part of partialObjectStream) {} // eslint-disable-line + + const { apmSpans, llmobsSpans } = await getEvents() + + const expectedCharacter = { name: 'Zara Nightshade', age: 28, height: "5'7\"" } + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'streamObject', + spanKind: 'workflow', + inputValue: 'Invent a character for a video game', + outputValue: JSON.stringify(expectedCharacter), + metadata: { + schema: MOCK_OBJECT, + output: 'object', + maxRetries: MOCK_NUMBER, + }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + const expectedLlmSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doStream', + inputMessages: [{ content: 'Invent a character for a video game', role: 'user' }], + outputMessages: [{ + content: JSON.stringify(expectedCharacter), + role: 'assistant' + }], + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' } + }) + + expect(llmobsSpans[0]).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmobsSpans[1]).to.deepEqualWithMockValues(expectedLlmSpan) + }) + + it('creates a span for a tool call', async () => { + let tools + let maxStepsArg = {} + const toolSchema = ai.jsonSchema({ + type: 'object', + properties: { + location: { type: 'string', description: 'The location to get the weather for' } + }, + required: ['location'] + }) + + if (semifies(realVersion, '>=5.0.0')) { + tools = { + weather: ai.tool({ + description: 'Get the weather in a given location', + inputSchema: toolSchema, + execute: async ({ location }) => ({ + location, + temperature: 72 + }) + }) + } + + maxStepsArg = { stopWhen: ai.stepCountIs(5) } + } else { + tools = [ai.tool({ + id: 'weather', + description: 'Get the weather in a given location', + parameters: toolSchema, + execute: async ({ location }) => ({ + location, + temperature: 72 + }) + })] + + maxStepsArg = { maxSteps: 5 } + } + + await ai.generateText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'What is the weather in Tokyo?', + tools, + ...maxStepsArg, + }) + + const { apmSpans, llmobsSpans } = await getEvents() + + const workflowSpan = llmobsSpans[0] + const llmSpan = llmobsSpans[1] + const toolCallSpan = llmobsSpans[2] + const llmSpan2 = llmobsSpans[3] + + const expectedFinalOutput = semifies(realVersion, '>=5.0.0') + ? 'The current temperature in Tokyo is 72°F. If you need more details about the weather, just let me know!' + : 'The current weather in Tokyo is 72°F.' + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'generateText', + spanKind: 'workflow', + inputValue: 'What is the weather in Tokyo?', + outputValue: expectedFinalOutput, + metadata: { + maxSteps: MOCK_NUMBER, + maxRetries: MOCK_NUMBER, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedLlmSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doGenerate', + inputMessages: [ + { content: 'You are a helpful assistant', role: 'system' }, + { content: 'What is the weather in Tokyo?', role: 'user' } + ], + outputMessages: [{ + content: MOCK_STRING, + role: 'assistant', + tool_calls: [{ + tool_id: MOCK_STRING, + name: 'weather', + arguments: { + location: 'Tokyo' + }, + type: 'function' + }] + }], + metadata: { + max_tokens: 100, + temperature: 0.5, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedToolCallSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[2], + parentId: llmobsSpans[0].span_id, + name: 'weather', + spanKind: 'tool', + inputValue: '{"location":"Tokyo"}', + outputValue: JSON.stringify({ location: 'Tokyo', temperature: 72 }), + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedLlmSpan2 = expectedLLMObsLLMSpanEvent({ + span: apmSpans[3], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doGenerate', + inputMessages: [ + { content: 'You are a helpful assistant', role: 'system' }, + { content: 'What is the weather in Tokyo?', role: 'user' }, + { + content: '', + role: 'assistant', + tool_calls: [{ + tool_id: MOCK_STRING, + name: 'weather', + arguments: { + location: 'Tokyo' + }, + type: 'function' + }] + }, + { + content: JSON.stringify({ location: 'Tokyo', temperature: 72 }), + role: 'tool', + tool_id: MOCK_STRING + } + ], + outputMessages: [{ content: expectedFinalOutput, role: 'assistant' }], + metadata: { + max_tokens: 100, + temperature: 0.5, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + expect(workflowSpan).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmSpan).to.deepEqualWithMockValues(expectedLlmSpan) + expect(toolCallSpan).to.deepEqualWithMockValues(expectedToolCallSpan) + expect(llmSpan2).to.deepEqualWithMockValues(expectedLlmSpan2) + }) + + it('created a span for a tool call from a stream', async () => { + let tools + let maxStepsArg = {} + const toolSchema = ai.jsonSchema({ + type: 'object', + properties: { + location: { type: 'string', description: 'The location to get the weather for' } + }, + required: ['location'] + }) + + if (semifies(realVersion, '>=5.0.0')) { + tools = { + weather: ai.tool({ + description: 'Get the weather in a given location', + inputSchema: toolSchema, + execute: async ({ location }) => ({ + location, + temperature: 72 + }) + }) + } + + maxStepsArg = { stopWhen: ai.stepCountIs(5) } + } else { + tools = [ai.tool({ + id: 'weather', + description: 'Get the weather in a given location', + parameters: toolSchema, + execute: async ({ location }) => ({ + location, + temperature: 72 + }) + })] + + maxStepsArg = { maxSteps: 5 } + } + + const result = await ai.streamText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant', + prompt: 'What is the weather in Tokyo?', + tools, + ...maxStepsArg, + }) + + const textStream = result.textStream + + for await (const part of textStream) {} // eslint-disable-line + + const { apmSpans, llmobsSpans } = await getEvents() + + const workflowSpan = llmobsSpans[0] + const llmSpan = llmobsSpans[1] + const toolCallSpan = llmobsSpans[2] + const llmSpan2 = llmobsSpans[3] + + const expectedFinalOutput = semifies(realVersion, '>=5.0.0') + ? 'The current temperature in Tokyo is 72°F. If you need more details or specific forecasts, feel free to ask!' + : 'The current weather in Tokyo is 72°F.' + + const expectedWorkflowSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[0], + name: 'streamText', + spanKind: 'workflow', + inputValue: 'What is the weather in Tokyo?', + outputValue: expectedFinalOutput, + metadata: { + maxSteps: MOCK_NUMBER, + maxRetries: MOCK_NUMBER, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedLlmSpan = expectedLLMObsLLMSpanEvent({ + span: apmSpans[1], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doStream', + inputMessages: [ + { content: 'You are a helpful assistant', role: 'system' }, + { content: 'What is the weather in Tokyo?', role: 'user' } + ], + outputMessages: [{ + content: MOCK_STRING, + role: 'assistant', + tool_calls: [{ + tool_id: MOCK_STRING, + name: 'weather', + arguments: { + location: 'Tokyo' + }, + type: 'function' + }] + }], + metadata: { + max_tokens: 100, + temperature: 0.5, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedToolCallSpan = expectedLLMObsNonLLMSpanEvent({ + span: apmSpans[2], + parentId: llmobsSpans[0].span_id, + /** + * MOCK_STRING used as the stream implementation for ai does not finish the initial llm spans + * first to associate the tool call id with the tool itself (by matching descriptions). + * + * Usually, this would mean the tool call name is 'toolCall'. + * + * However, because we used mocked responses, the second time this test is called, the tool call + * will have the name 'weather' instead. We just assert that the name exists and is a string to simplify. + */ + name: MOCK_STRING, + spanKind: 'tool', + inputValue: JSON.stringify({ location: 'Tokyo' }), + outputValue: JSON.stringify({ location: 'Tokyo', temperature: 72 }), + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + const expectedLlmSpan2 = expectedLLMObsLLMSpanEvent({ + span: apmSpans[3], + parentId: llmobsSpans[0].span_id, + spanKind: 'llm', + modelName: 'gpt-4o-mini', + modelProvider: 'openai', + name: 'doStream', + inputMessages: [ + { content: 'You are a helpful assistant', role: 'system' }, + { content: 'What is the weather in Tokyo?', role: 'user' }, + { + content: '', + role: 'assistant', + tool_calls: [{ + tool_id: MOCK_STRING, + name: 'weather', + arguments: { + location: 'Tokyo' + }, + type: 'function' + }] + }, + { + content: JSON.stringify({ location: 'Tokyo', temperature: 72 }), + role: 'tool', + tool_id: MOCK_STRING + } + ], + outputMessages: [{ content: expectedFinalOutput, role: 'assistant' }], + metadata: { + max_tokens: 100, + temperature: 0.5, + }, + tokenMetrics: { input_tokens: MOCK_NUMBER, output_tokens: MOCK_NUMBER, total_tokens: MOCK_NUMBER }, + tags: { ml_app: 'test', language: 'javascript', integration: 'ai' }, + }) + + expect(workflowSpan).to.deepEqualWithMockValues(expectedWorkflowSpan) + expect(llmSpan).to.deepEqualWithMockValues(expectedLlmSpan) + expect(toolCallSpan).to.deepEqualWithMockValues(expectedToolCallSpan) + expect(llmSpan2).to.deepEqualWithMockValues(expectedLlmSpan2) + }) + }) +}) diff --git a/packages/dd-trace/test/llmobs/plugins/aws-sdk/bedrockruntime.spec.js b/packages/dd-trace/test/llmobs/plugins/aws-sdk/bedrockruntime.spec.js index ad5494095c1..1014007c948 100644 --- a/packages/dd-trace/test/llmobs/plugins/aws-sdk/bedrockruntime.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/aws-sdk/bedrockruntime.spec.js @@ -1,6 +1,7 @@ 'use strict' const agent = require('../../../plugins/agent') +const { withVersions } = require('../../../setup/mocha') const nock = require('nock') const { expectedLLMObsLLMSpanEvent, deepEqualWithMockValues } = require('../../util') diff --git a/packages/dd-trace/test/llmobs/plugins/google-cloud-vertexai/index.spec.js b/packages/dd-trace/test/llmobs/plugins/google-cloud-vertexai/index.spec.js index 012f6bc1ec7..d4bf7f6eeb3 100644 --- a/packages/dd-trace/test/llmobs/plugins/google-cloud-vertexai/index.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/google-cloud-vertexai/index.spec.js @@ -2,6 +2,7 @@ const LLMObsSpanWriter = require('../../../../src/llmobs/writers/spans') const agent = require('../../../plugins/agent') +const { withVersions } = require('../../../setup/mocha') const { expectedLLMObsLLMSpanEvent, deepEqualWithMockValues diff --git a/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js b/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js index a647f39e7a8..39fd855162d 100644 --- a/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/langchain/index.spec.js @@ -4,6 +4,7 @@ const LLMObsSpanWriter = require('../../../../src/llmobs/writers/spans') const { useEnv } = require('../../../../../../integration-tests/helpers') const agent = require('../../../../../dd-trace/test/plugins/agent') const iastFilter = require('../../../../src/appsec/iast/taint-tracking/filter') +const { withVersions } = require('../../../setup/mocha') const { expectedLLMObsLLMSpanEvent, @@ -14,46 +15,10 @@ const { } = require('../../util') const chai = require('chai') -const semver = require('semver') - chai.Assertion.addMethod('deepEqualWithMockValues', deepEqualWithMockValues) -const nock = require('nock') -function stubCall ({ base = '', path = '', code = 200, response = {} }) { - const responses = Array.isArray(response) ? response : [response] - const times = responses.length - nock(base).post(path).times(times).reply(() => { - return [code, responses.shift()] - }) -} - -const openAiBaseCompletionInfo = { base: 'https://api.openai.com', path: '/v1/completions' } -const openAiBaseChatInfo = { base: 'https://api.openai.com', path: '/v1/chat/completions' } -const openAiBaseEmbeddingInfo = { base: 'https://api.openai.com', path: '/v1/embeddings' } - const isDdTrace = iastFilter.isDdTrace -function stubSingleEmbedding (langchainOpenaiOpenAiVersion) { - if (semver.satisfies(langchainOpenaiOpenAiVersion, '>=4.91.0')) { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: require('../../../../../datadog-plugin-langchain/test/fixtures/single-embedding.json') - }) - } else { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: { - object: 'list', - data: [{ - object: 'embedding', - index: 0, - embedding: Array(1536).fill(0) - }] - } - }) - } -} - describe('integrations', () => { let langchainOpenai let langchainAnthropic @@ -66,24 +31,50 @@ describe('integrations', () => { let tool let MemoryVectorStore - /** - * In OpenAI 4.91.0, the default response format for embeddings was changed from `float` to `base64`. - * We do not have control in @langchain/openai embeddings to change this for an individual call, - * so we need to check the version and stub the response accordingly. If the OpenAI version installed with - * @langchain/openai is less than 4.91.0, we stub the response to be a float array of zeros. - * If it is 4.91.0 or greater, we stub with a pre-recorded fixture of a 1536 base64 encoded embedding. - */ - let langchainOpenaiOpenAiVersion - let llmobs - // so we can verify it gets tagged properly useEnv({ OPENAI_API_KEY: '', ANTHROPIC_API_KEY: '', COHERE_API_KEY: '' }) + function getLangChainOpenAiClient (type = 'llm', options = {}) { + Object.assign(options, { + configuration: { + baseURL: 'http://127.0.0.1:9126/vcr/openai' + } + }) + + if (type === 'llm') { + return new langchainOpenai.OpenAI(options) + } + + if (type === 'chat') { + return new langchainOpenai.ChatOpenAI(options) + } + + if (type === 'embedding') { + return new langchainOpenai.OpenAIEmbeddings(options) + } + + throw new Error(`Invalid type: ${type}`) + } + + function getLangChainAnthropicClient (type = 'chat', options = {}) { + Object.assign(options, { + clientOptions: { + baseURL: 'http://127.0.0.1:9126/vcr/anthropic' + } + }) + + if (type === 'chat') { + return new langchainAnthropic.ChatAnthropic(options) + } + + throw new Error(`Invalid type: ${type}`) + } + describe('langchain', () => { before(async () => { sinon.stub(LLMObsSpanWriter.prototype, 'append') @@ -111,7 +102,6 @@ describe('integrations', () => { }) afterEach(() => { - nock.cleanAll() LLMObsSpanWriter.prototype.append.reset() }) @@ -122,8 +112,7 @@ describe('integrations', () => { return agent.close({ ritmReset: false, wipe: true }) }) - // TODO(sabrenner): remove this once we have the more robust mocking merged - withVersions('langchain', ['@langchain/core'], '<0.3.60', version => { + withVersions('langchain', ['@langchain/core'], version => { describe('langchain', () => { beforeEach(() => { langchainOpenai = require(`../../../../../../versions/langchain@${version}`) @@ -148,28 +137,11 @@ describe('integrations', () => { MemoryVectorStore = require(`../../../../../../versions/@langchain/core@${version}`) .get('langchain/vectorstores/memory') .MemoryVectorStore - - langchainOpenaiOpenAiVersion = - require(`../../../../../../versions/langchain@${version}`) - .get('openai/version') - .VERSION }) describe('llm', () => { it('submits an llm span for an openai llm call', async () => { - stubCall({ - ...openAiBaseCompletionInfo, - response: { - choices: [ - { - text: 'Hello, world!' - } - ], - usage: { prompt_tokens: 8, completion_tokens: 12, otal_tokens: 20 } - } - }) - - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct' }) + const llm = getLangChainOpenAiClient('llm', { model: 'gpt-3.5-turbo-instruct' }) const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] @@ -181,24 +153,22 @@ describe('integrations', () => { modelName: 'gpt-3.5-turbo-instruct', modelProvider: 'openai', name: 'langchain.llms.openai.OpenAI', - inputMessages: [{ content: 'Hello!' }], - outputMessages: [{ content: 'Hello, world!' }], + inputMessages: [{ content: 'What is 2 + 2?' }], + outputMessages: [{ content: '\n\n4' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 8, output_tokens: 12, total_tokens: 20 }, + tokenMetrics: { input_tokens: 8, output_tokens: 2, total_tokens: 10 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) expect(spanEvent).to.deepEqualWithMockValues(expected) }) - await llm.invoke('Hello!') + await llm.invoke('What is 2 + 2?') await checkTraces }) it('does not tag output if there is an error', async () => { - nock('https://api.openai.com').post('/v1/completions').reply(500) - const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] const spanEvent = LLMObsSpanWriter.prototype.append.getCall(0).args[0] @@ -206,7 +176,7 @@ describe('integrations', () => { const expected = expectedLLMObsLLMSpanEvent({ span, spanKind: 'llm', - modelName: 'gpt-3.5-turbo-instruct', + modelName: 'text-embedding-3-small', modelProvider: 'openai', name: 'langchain.llms.openai.OpenAI', inputMessages: [{ content: 'Hello!' }], @@ -223,7 +193,7 @@ describe('integrations', () => { expect(spanEvent).to.deepEqualWithMockValues(expected) }) - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct', maxRetries: 0 }) + const llm = new langchainOpenai.OpenAI({ model: 'text-embedding-3-small', maxRetries: 0 }) try { await llm.invoke('Hello!') @@ -286,22 +256,7 @@ describe('integrations', () => { describe('chat model', () => { it('submits an llm span for an openai chat model call', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - choices: [ - { - message: { - content: 'Hello, world!', - role: 'assistant' - } - } - ], - usage: { prompt_tokens: 8, completion_tokens: 12, total_tokens: 20 } - } - }) - - const chat = new langchainOpenai.ChatOpenAI({ model: 'gpt-3.5-turbo' }) + const chat = getLangChainOpenAiClient('chat', { model: 'gpt-3.5-turbo' }) const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] @@ -313,24 +268,22 @@ describe('integrations', () => { modelName: 'gpt-3.5-turbo', modelProvider: 'openai', name: 'langchain.chat_models.openai.ChatOpenAI', - inputMessages: [{ content: 'Hello!', role: 'user' }], - outputMessages: [{ content: 'Hello, world!', role: 'assistant' }], + inputMessages: [{ content: 'What is 2 + 2?', role: 'user' }], + outputMessages: [{ content: '2 + 2 = 4', role: 'assistant' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 8, output_tokens: 12, total_tokens: 20 }, + tokenMetrics: { input_tokens: 15, output_tokens: 7, total_tokens: 22 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) expect(spanEvent).to.deepEqualWithMockValues(expected) }) - await chat.invoke('Hello!') + await chat.invoke('What is 2 + 2?') await checkTraces }) it('does not tag output if there is an error', async () => { - nock('https://api.openai.com').post('/v1/chat/completions').reply(500) - const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] const spanEvent = LLMObsSpanWriter.prototype.append.getCall(0).args[0] @@ -338,7 +291,7 @@ describe('integrations', () => { const expected = expectedLLMObsLLMSpanEvent({ span, spanKind: 'llm', - modelName: 'gpt-3.5-turbo', + modelName: 'gpt-3.5-turbo-instruct', modelProvider: 'openai', name: 'langchain.chat_models.openai.ChatOpenAI', inputMessages: [{ content: 'Hello!', role: 'user' }], @@ -355,7 +308,7 @@ describe('integrations', () => { expect(spanEvent).to.deepEqualWithMockValues(expected) }) - const chat = new langchainOpenai.ChatOpenAI({ model: 'gpt-3.5-turbo', maxRetries: 0 }) + const chat = new langchainOpenai.ChatOpenAI({ model: 'gpt-3.5-turbo-instruct', maxRetries: 0 }) try { await chat.invoke('Hello!') @@ -365,23 +318,6 @@ describe('integrations', () => { }) it('submits an llm span for an anthropic chat model call', async () => { - stubCall({ - base: 'https://api.anthropic.com', - path: '/v1/messages', - response: { - id: 'msg_01NE2EJQcjscRyLbyercys6p', - type: 'message', - role: 'assistant', - model: 'claude-2.1', - content: [ - { type: 'text', text: 'Hello!' } - ], - stop_reason: 'end_turn', - stop_sequence: null, - usage: { input_tokens: 11, output_tokens: 6 } - } - }) - const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] const spanEvent = LLMObsSpanWriter.prototype.append.getCall(0).args[0] @@ -389,20 +325,20 @@ describe('integrations', () => { const expected = expectedLLMObsLLMSpanEvent({ span, spanKind: 'llm', - modelName: 'claude-2.1', // overriden langchain for older versions + modelName: 'claude-3-5-sonnet-20241022', modelProvider: 'anthropic', name: 'langchain.chat_models.anthropic.ChatAnthropic', inputMessages: [{ content: 'Hello!', role: 'user' }], - outputMessages: [{ content: 'Hello!', role: 'assistant' }], + outputMessages: [{ content: 'Hi there! How can I help you today?', role: 'assistant' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 11, output_tokens: 6, total_tokens: 17 }, + tokenMetrics: { input_tokens: 9, output_tokens: 13, total_tokens: 22 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) expect(spanEvent).to.deepEqualWithMockValues(expected) }) - const chatModel = new langchainAnthropic.ChatAnthropic({ model: 'claude-2.1' }) + const chatModel = getLangChainAnthropicClient('chat', { modelName: 'claude-3-5-sonnet-20241022' }) await chatModel.invoke('Hello!') @@ -410,31 +346,6 @@ describe('integrations', () => { }) it('submits an llm span with tool calls', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - model: 'gpt-4', - choices: [{ - message: { - role: 'assistant', - content: null, - tool_calls: [ - { - id: 'tool-1', - type: 'function', - function: { - name: 'extract_fictional_info', - arguments: '{"name":"SpongeBob","origin":"Bikini Bottom"}' - } - } - ] - }, - finish_reason: 'tool_calls', - index: 0 - }] - } - }) - const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] const spanEvent = LLMObsSpanWriter.prototype.append.getCall(0).args[0] @@ -458,8 +369,7 @@ describe('integrations', () => { }] }], metadata: MOCK_ANY, - // also tests tokens not sent on llm-type spans should be 0 - tokenMetrics: { input_tokens: 0, output_tokens: 0, total_tokens: 0 }, + tokenMetrics: { input_tokens: 82, output_tokens: 31, total_tokens: 113 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -468,19 +378,22 @@ describe('integrations', () => { const tools = [ { - name: 'extract_fictional_info', - description: 'Get the fictional information from the body of the input text', - parameters: { - type: 'object', - properties: { - name: { type: 'string', description: 'Name of the character' }, - origin: { type: 'string', description: 'Where they live' } + type: 'function', + function: { + name: 'extract_fictional_info', + description: 'Get the fictional information from the body of the input text', + parameters: { + type: 'object', + properties: { + name: { type: 'string', description: 'Name of the character' }, + origin: { type: 'string', description: 'Where they live' } + } } } } ] - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const modelWithTools = model.bindTools(tools) await modelWithTools.invoke('My name is SpongeBob and I live in Bikini Bottom.') @@ -491,26 +404,7 @@ describe('integrations', () => { describe('embedding', () => { it('submits an embedding span for an `embedQuery` call', async () => { - if (semver.satisfies(langchainOpenaiOpenAiVersion, '>=4.91.0')) { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: require('../../../../../datadog-plugin-langchain/test/fixtures/single-embedding.json') - }) - } else { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: { - object: 'list', - data: [{ - object: 'embedding', - index: 0, - embedding: Array(1536).fill(0) - }] - } - }) - } - - const embeddings = new langchainOpenai.OpenAIEmbeddings() + const embeddings = getLangChainOpenAiClient('embedding') const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] @@ -522,7 +416,7 @@ describe('integrations', () => { modelName: 'text-embedding-ada-002', modelProvider: 'openai', name: 'langchain.embeddings.openai.OpenAIEmbeddings', - inputDocuments: [{ text: 'Hello!' }], + inputDocuments: [{ text: 'Hello, world!' }], outputValue: '[1 embedding(s) returned with size 1536]', metadata: MOCK_ANY, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } @@ -531,14 +425,12 @@ describe('integrations', () => { expect(spanEvent).to.deepEqualWithMockValues(expected) }) - await embeddings.embedQuery('Hello!') + await embeddings.embedQuery('Hello, world!') await checkTraces }) it('does not tag output if there is an error', async () => { - nock('https://api.openai.com').post('/v1/embeddings').reply(500) - const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] const spanEvent = LLMObsSpanWriter.prototype.append.getCall(0).args[0] @@ -546,10 +438,10 @@ describe('integrations', () => { const expected = expectedLLMObsLLMSpanEvent({ span, spanKind: 'embedding', - modelName: 'text-embedding-ada-002', + modelName: 'gpt-3.5-turbo-instruct', modelProvider: 'openai', name: 'langchain.embeddings.openai.OpenAIEmbeddings', - inputDocuments: [{ text: 'Hello!' }], + inputDocuments: [{ text: 'Hello, world!' }], outputValue: '', metadata: MOCK_ANY, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' }, @@ -562,40 +454,17 @@ describe('integrations', () => { expect(spanEvent).to.deepEqualWithMockValues(expected) }) - const embeddings = new langchainOpenai.OpenAIEmbeddings({ maxRetries: 0 }) + const embeddings = getLangChainOpenAiClient('embedding', { model: 'gpt-3.5-turbo-instruct' }) try { - await embeddings.embedQuery('Hello!') + await embeddings.embedQuery('Hello, world!') } catch {} await checkTraces }) it('submits an embedding span for an `embedDocuments` call', async () => { - if (semver.satisfies(langchainOpenaiOpenAiVersion, '>=4.91.0')) { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: require('../../../../../datadog-plugin-langchain/test/fixtures/double-embedding.json') - }) - } else { - stubCall({ - ...openAiBaseEmbeddingInfo, - response: { - object: 'list', - data: [{ - object: 'embedding', - index: 0, - embedding: Array(1536).fill(0) - }, { - object: 'embedding', - index: 1, - embedding: Array(1536).fill(0) - }] - } - }) - } - - const embeddings = new langchainOpenai.OpenAIEmbeddings() + const embeddings = getLangChainOpenAiClient('embedding') const checkTraces = agent.assertSomeTraces(traces => { const span = traces[0][0] @@ -607,7 +476,7 @@ describe('integrations', () => { modelName: 'text-embedding-ada-002', modelProvider: 'openai', name: 'langchain.embeddings.openai.OpenAIEmbeddings', - inputDocuments: [{ text: 'Hello!' }, { text: 'World!' }], + inputDocuments: [{ text: 'Hello, world!' }, { text: 'Goodbye, world!' }], outputValue: '[2 embedding(s) returned with size 1536]', metadata: MOCK_ANY, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } @@ -616,7 +485,7 @@ describe('integrations', () => { expect(spanEvent).to.deepEqualWithMockValues(expected) }) - await embeddings.embedDocuments(['Hello!', 'World!']) + await embeddings.embedDocuments(['Hello, world!', 'Goodbye, world!']) await checkTraces }) @@ -624,24 +493,12 @@ describe('integrations', () => { describe('chain', () => { it('submits a workflow and llm spans for a simple chain call', async () => { - stubCall({ - ...openAiBaseCompletionInfo, - response: { - choices: [ - { - text: 'LangSmith can help with testing in several ways.' - } - ], - usage: { prompt_tokens: 8, completion_tokens: 12, otal_tokens: 20 } - } - }) - const prompt = langchainPrompts.ChatPromptTemplate.fromMessages([ ['system', 'You are a world class technical documentation writer'], ['user', '{input}'] ]) - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct' }) + const llm = getLangChainOpenAiClient('llm', { model: 'gpt-3.5-turbo-instruct' }) const chain = prompt.pipe(llm) @@ -653,12 +510,19 @@ describe('integrations', () => { const workflowSpanEvent = LLMObsSpanWriter.prototype.append.getCall(0).args[0] const llmSpanEvent = LLMObsSpanWriter.prototype.append.getCall(1).args[0] + const expectedOutput = '\n\nSystem: LangSmith is a top-of-the-line software that caters to the needs ' + + 'of technical writers like you. It offers a user-friendly interface, advanced formatting tools, and ' + + 'collaboration features to help you create high-quality technical documents with ease. With LangSmith, ' + + 'you can produce professional-looking manuals, guides, and tutorials that will impress even the most ' + + 'discerning clients. Its robust features and intuitive design make it the go-to tool for ' + + 'technical writers all over the world.' + const expectedWorkflow = expectedLLMObsNonLLMSpanEvent({ span: workflowSpan, spanKind: 'workflow', name: 'langchain_core.runnables.RunnableSequence', inputValue: JSON.stringify({ input: 'Can you tell me about LangSmith?' }), - outputValue: 'LangSmith can help with testing in several ways.', + outputValue: expectedOutput, metadata: MOCK_ANY, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -670,14 +534,13 @@ describe('integrations', () => { modelName: 'gpt-3.5-turbo-instruct', modelProvider: 'openai', name: 'langchain.llms.openai.OpenAI', - // this is how LangChain formats these IOs for LLMs inputMessages: [{ content: 'System: You are a world class technical documentation writer\n' + 'Human: Can you tell me about LangSmith?' }], - outputMessages: [{ content: 'LangSmith can help with testing in several ways.' }], + outputMessages: [{ content: expectedOutput }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 8, output_tokens: 12, total_tokens: 20 }, + tokenMetrics: { input_tokens: 21, output_tokens: 94, total_tokens: 115 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -692,8 +555,6 @@ describe('integrations', () => { }) it('does not tag output if there is an error', async () => { - nock('https://api.openai.com').post('/v1/completions').reply(500) - const checkTraces = agent.assertSomeTraces(traces => { const spans = traces[0] @@ -718,7 +579,7 @@ describe('integrations', () => { expect(workflowSpanEvent).to.deepEqualWithMockValues(expectedWorkflow) }) - const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct', maxRetries: 0 }) + const llm = getLangChainOpenAiClient('llm', { model: 'text-embedding-3-small', maxRetries: 0 }) const parser = new langchainOutputParsers.StringOutputParser() const chain = llm.pipe(parser) @@ -730,40 +591,12 @@ describe('integrations', () => { }) it('submits workflow and llm spans for a nested chain', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: [ - { - choices: [ - { - message: { - content: 'Springfield, Illinois', - role: 'assistant' - } - } - ], - usage: { prompt_tokens: 8, completion_tokens: 12, total_tokens: 20 } - }, - { - choices: [ - { - message: { - content: 'Springfield, Illinois está en los Estados Unidos.', - role: 'assistant' - } - } - ], - usage: { prompt_tokens: 8, completion_tokens: 12, total_tokens: 20 } - } - ] - }) - const firstPrompt = langchainPrompts.ChatPromptTemplate.fromTemplate('what is the city {person} is from?') const secondPrompt = langchainPrompts.ChatPromptTemplate.fromTemplate( 'what country is the city {city} in? respond in {language}' ) - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-3.5-turbo' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-3.5-turbo' }) const parser = new langchainOutputParsers.StringOutputParser() const firstChain = firstPrompt.pipe(model).pipe(parser) @@ -792,12 +625,15 @@ describe('integrations', () => { const secondSubWorkflowSpanEvent = LLMObsSpanWriter.prototype.append.getCall(3).args[0] const secondLLMSpanEvent = LLMObsSpanWriter.prototype.append.getCall(4).args[0] + const expectedOutput = 'Abraham Lincoln nació en Hodgenville, Kentucky. ' + + 'Más tarde vivió en Springfield, Illinois, que se asocia frecuentemente con él como su ciudad natal.' + const expectedTopLevelWorkflow = expectedLLMObsNonLLMSpanEvent({ span: topLevelWorkflow, spanKind: 'workflow', name: 'langchain_core.runnables.RunnableSequence', inputValue: JSON.stringify({ person: 'Abraham Lincoln', language: 'Spanish' }), - outputValue: 'Springfield, Illinois está en los Estados Unidos.', + outputValue: expectedOutput, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -807,7 +643,8 @@ describe('integrations', () => { spanKind: 'workflow', name: 'langchain_core.runnables.RunnableSequence', inputValue: JSON.stringify({ person: 'Abraham Lincoln', language: 'Spanish' }), - outputValue: 'Springfield, Illinois', + outputValue: 'Abraham Lincoln was born in Hodgenville, Kentucky. He later lived ' + + 'in Springfield, Illinois, which is often associated with him as his home city.', tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -821,9 +658,13 @@ describe('integrations', () => { inputMessages: [ { content: 'what is the city Abraham Lincoln is from?', role: 'user' } ], - outputMessages: [{ content: 'Springfield, Illinois', role: 'assistant' }], + outputMessages: [{ + content: 'Abraham Lincoln was born in Hodgenville, Kentucky. He later lived ' + + 'in Springfield, Illinois, which is often associated with him as his home city.', + role: 'assistant' + }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 8, output_tokens: 12, total_tokens: 20 }, + tokenMetrics: { input_tokens: 16, output_tokens: 30, total_tokens: 46 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -832,8 +673,12 @@ describe('integrations', () => { parentId: topLevelWorkflow.span_id, spanKind: 'workflow', name: 'langchain_core.runnables.RunnableSequence', - inputValue: JSON.stringify({ language: 'Spanish', city: 'Springfield, Illinois' }), - outputValue: 'Springfield, Illinois está en los Estados Unidos.', + inputValue: JSON.stringify({ + language: 'Spanish', + city: 'Abraham Lincoln was born in Hodgenville, Kentucky. He later lived in ' + + 'Springfield, Illinois, which is often associated with him as his home city.' + }), + outputValue: expectedOutput, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -845,11 +690,16 @@ describe('integrations', () => { modelProvider: 'openai', name: 'langchain.chat_models.openai.ChatOpenAI', inputMessages: [ - { content: 'what country is the city Springfield, Illinois in? respond in Spanish', role: 'user' } + { + content: 'what country is the city Abraham Lincoln was born in Hodgenville, Kentucky. ' + + 'He later lived in Springfield, Illinois, which is often associated with him as his home city. ' + + 'in? respond in Spanish', + role: 'user' + } ], - outputMessages: [{ content: 'Springfield, Illinois está en los Estados Unidos.', role: 'assistant' }], + outputMessages: [{ content: expectedOutput, role: 'assistant' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 8, output_tokens: 12, total_tokens: 20 }, + tokenMetrics: { input_tokens: 46, output_tokens: 37, total_tokens: 83 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -861,51 +711,17 @@ describe('integrations', () => { }) const result = await completeChain.invoke({ person: 'Abraham Lincoln', language: 'Spanish' }) - expect(result).to.equal('Springfield, Illinois está en los Estados Unidos.') + expect(result).to.exist await checkTraces }) it('submits workflow and llm spans for a batched chain', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: [ - { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Why did the chicken cross the road? To get to the other side!' - } - }] - }, - { - model: 'gpt-4', - usage: { - prompt_tokens: 37, - completion_tokens: 10, - total_tokens: 47 - }, - choices: [{ - message: { - role: 'assistant', - content: 'Why was the dog confused? It was barking up the wrong tree!' - } - }] - } - ] - }) - const prompt = langchainPrompts.ChatPromptTemplate.fromTemplate( 'Tell me a joke about {topic}' ) const parser = new langchainOutputParsers.StringOutputParser() - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-4' }) const chain = langchainRunnables.RunnableSequence.from([ { @@ -933,9 +749,9 @@ describe('integrations', () => { name: 'langchain_core.runnables.RunnableSequence', inputValue: JSON.stringify(['chickens', 'dogs']), outputValue: JSON.stringify([ - 'Why did the chicken cross the road? To get to the other side!', - 'Why was the dog confused? It was barking up the wrong tree!' - ]), + "Why don't chickens use Facebook?\n\nBecause they already know what everyone's clucking about!", + 'Why did the scarecrow adopt a dog?\n\nBecause he needed a "barking" buddy!'] + ), tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -948,11 +764,12 @@ describe('integrations', () => { name: 'langchain.chat_models.openai.ChatOpenAI', inputMessages: [{ content: 'Tell me a joke about chickens', role: 'user' }], outputMessages: [{ - content: 'Why did the chicken cross the road? To get to the other side!', + content: "Why don't chickens use Facebook?\n\nBecause " + + "they already know what everyone's clucking about!", role: 'assistant' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 37, output_tokens: 10, total_tokens: 47 }, + tokenMetrics: { input_tokens: 13, output_tokens: 18, total_tokens: 31 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -965,11 +782,11 @@ describe('integrations', () => { name: 'langchain.chat_models.openai.ChatOpenAI', inputMessages: [{ content: 'Tell me a joke about dogs', role: 'user' }], outputMessages: [{ - content: 'Why was the dog confused? It was barking up the wrong tree!', + content: 'Why did the scarecrow adopt a dog?\n\nBecause he needed a "barking" buddy!', role: 'assistant' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 37, output_tokens: 10, total_tokens: 47 }, + tokenMetrics: { input_tokens: 13, output_tokens: 19, total_tokens: 32 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -984,28 +801,13 @@ describe('integrations', () => { }) it('submits a workflow and llm spans for different schema IO', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - choices: [ - { - message: { - content: 'Mitochondria', - role: 'assistant' - } - } - ], - usage: { prompt_tokens: 8, completion_tokens: 12, total_tokens: 20 } - } - }) - const prompt = langchainPrompts.ChatPromptTemplate.fromMessages([ ['system', 'You are an assistant who is good at {ability}. Respond in 20 words or fewer'], new langchainPrompts.MessagesPlaceholder('history'), ['human', '{input}'] ]) - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-3.5-turbo' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-3.5-turbo' }) const chain = prompt.pipe(model) const checkTraces = agent.assertSomeTraces(traces => { @@ -1070,7 +872,7 @@ describe('integrations', () => { ], outputMessages: [{ content: 'Mitochondria', role: 'assistant' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 8, output_tokens: 12, total_tokens: 20 }, + tokenMetrics: { input_tokens: 54, output_tokens: 3, total_tokens: 57 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -1091,21 +893,6 @@ describe('integrations', () => { }) it('traces a manually-instrumented step', async () => { - stubCall({ - ...openAiBaseChatInfo, - response: { - choices: [ - { - message: { - content: '3 squared is 9', - role: 'assistant' - } - } - ], - usage: { prompt_tokens: 8, completion_tokens: 12, total_tokens: 20 } - } - }) - let lengthFunction = (input = { foo: '' }) => { llmobs.annotate({ inputData: input }) // so we don't try and tag `config` with auto-annotation return { @@ -1114,7 +901,7 @@ describe('integrations', () => { } lengthFunction = llmobs.wrap({ kind: 'task' }, lengthFunction) - const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4o' }) + const model = getLangChainOpenAiClient('chat', { model: 'gpt-4o' }) const prompt = langchainPrompts.ChatPromptTemplate.fromTemplate('What is {length} squared?') @@ -1140,7 +927,7 @@ describe('integrations', () => { spanKind: 'workflow', name: 'langchain_core.runnables.RunnableSequence', inputValue: JSON.stringify({ foo: 'bar' }), - outputValue: '3 squared is 9', + outputValue: '3 squared is 9.', tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -1162,9 +949,9 @@ describe('integrations', () => { modelProvider: 'openai', name: 'langchain.chat_models.openai.ChatOpenAI', inputMessages: [{ content: 'What is 3 squared?', role: 'user' }], - outputMessages: [{ content: '3 squared is 9', role: 'assistant' }], + outputMessages: [{ content: '3 squared is 9.', role: 'assistant' }], metadata: MOCK_ANY, - tokenMetrics: { input_tokens: 8, output_tokens: 12, total_tokens: 20 }, + tokenMetrics: { input_tokens: 13, output_tokens: 6, total_tokens: 19 }, tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) @@ -1268,9 +1055,7 @@ describe('integrations', () => { let vectorstore beforeEach(() => { - stubSingleEmbedding(langchainOpenaiOpenAiVersion) - - const embeddings = new langchainOpenai.OpenAIEmbeddings() + const embeddings = getLangChainOpenAiClient('embedding') vectorstore = new MemoryVectorStore(embeddings) const document = { @@ -1282,8 +1067,6 @@ describe('integrations', () => { }) it('submits a retrieval span with a child embedding span for similaritySearch', async () => { - stubSingleEmbedding(langchainOpenaiOpenAiVersion) - const checkTraces = agent.assertSomeTraces(traces => { const spans = traces[0] // first trace is the embedding call from the beforeEach @@ -1319,8 +1102,6 @@ describe('integrations', () => { }) it('submits a retrieval span with a child embedding span for similaritySearchWithScore', async () => { - stubSingleEmbedding(langchainOpenaiOpenAiVersion) - const checkTraces = agent.assertSomeTraces(traces => { const spans = traces[0] // first trace is the embedding call from the beforeEach @@ -1343,7 +1124,7 @@ describe('integrations', () => { outputDocuments: [{ text: 'The powerhouse of the cell is the mitochondria', name: 'https://example.com', - score: 1 + score: 0.7882083567178202 }], tags: { ml_app: 'test', language: 'javascript', integration: 'langchain' } }) diff --git a/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js b/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js index 01bce1241c8..77c3ea2ee7e 100644 --- a/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js @@ -4,6 +4,7 @@ const agent = require('../../../plugins/agent') const Sampler = require('../../../../src/sampler') const { DogStatsDClient } = require('../../../../src/dogstatsd') const { NoopExternalLogger } = require('../../../../src/external-logger/src') +const { withVersions } = require('../../../setup/mocha') const { expectedLLMObsLLMSpanEvent, deepEqualWithMockValues, MOCK_STRING, MOCK_NUMBER } = require('../../util') const chai = require('chai') diff --git a/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js b/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js index bbbfa5352e1..ad95baab645 100644 --- a/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js @@ -4,6 +4,7 @@ const agent = require('../../../plugins/agent') const Sampler = require('../../../../src/sampler') const { DogStatsDClient } = require('../../../../src/dogstatsd') const { NoopExternalLogger } = require('../../../../src/external-logger/src') +const { withVersions } = require('../../../setup/mocha') const { expectedLLMObsLLMSpanEvent, deepEqualWithMockValues, MOCK_STRING, MOCK_NUMBER } = require('../../util') const chai = require('chai') diff --git a/packages/dd-trace/test/llmobs/util.js b/packages/dd-trace/test/llmobs/util.js index 7ca394eb2bf..4ebae03e751 100644 --- a/packages/dd-trace/test/llmobs/util.js +++ b/packages/dd-trace/test/llmobs/util.js @@ -6,19 +6,23 @@ const tracerVersion = require('../../../../package.json').version const MOCK_STRING = Symbol('string') const MOCK_NUMBER = Symbol('number') +const MOCK_OBJECT = Symbol('object') const MOCK_ANY = Symbol('any') function deepEqualWithMockValues (expected) { const actual = this._obj - for (const key in actual) { + for (const key of Object.keys(actual)) { if (expected[key] === MOCK_STRING) { new chai.Assertion(typeof actual[key], `key ${key}`).to.equal('string') } else if (expected[key] === MOCK_NUMBER) { new chai.Assertion(typeof actual[key], `key ${key}`).to.equal('number') + } else if (expected[key] === MOCK_OBJECT) { + new chai.Assertion(typeof actual[key], `key ${key}`).to.equal('object') } else if (expected[key] === MOCK_ANY) { new chai.Assertion(actual[key], `key ${key}`).to.exist } else if (Array.isArray(expected[key])) { + assert.ok(Array.isArray(actual[key]), `key "${key}" is not an array`) const sortedExpected = [...expected[key].sort()] const sortedActual = [...actual[key].sort()] new chai.Assertion(sortedActual, `key: ${key}`).to.deepEqualWithMockValues(sortedExpected) @@ -190,11 +194,78 @@ function fromBuffer (spanProperty, isNumber = false) { return isNumber ? Number(strVal) : strVal } +const agent = require('../plugins/agent') +const assert = require('node:assert') + +/** + * @param {Object} options + * @param {string} options.plugin + * @param {Object} options.tracerConfigOptions + * @param {Object} options.closeOptions + * @returns {function(): Promise<{ apmSpans: Array, llmobsSpans: Array }>} + */ +function useLlmobs ({ + plugin, + tracerConfigOptions = { + llmobs: { + mlApp: 'test', + agentlessEnabled: false + } + }, + closeOptions = { ritmReset: false, wipe: true } +}) { + if (!plugin) { + throw new TypeError( + '`plugin` is required when using `useLlmobs`' + ) + } + + if (!tracerConfigOptions.llmobs) { + throw new TypeError( + '`loadOptions.llmobs` is required when using `useLlmobs`' + ) + } + + let apmTracesPromise + let llmobsTracesPromise + + before(() => { + return agent.load(plugin, {}, tracerConfigOptions) + }) + + beforeEach(() => { + apmTracesPromise = agent.assertSomeTraces(apmTraces => { + return apmTraces + .flatMap(trace => trace) + .sort((a, b) => a.start < b.start ? -1 : (a.start > b.start ? 1 : 0)) + }) + + llmobsTracesPromise = agent.useLlmobsTraces(llmobsTraces => { + return llmobsTraces + .flatMap(trace => trace) + .map(trace => trace.spans[0]) + .sort((a, b) => a.start_ns - b.start_ns) + }) + }) + + after(() => { + return agent.close(closeOptions) + }) + + return async function () { + const [apmSpans, llmobsSpans] = await Promise.all([apmTracesPromise, llmobsTracesPromise]) + + return { apmSpans, llmobsSpans } + } +} + module.exports = { expectedLLMObsLLMSpanEvent, expectedLLMObsNonLLMSpanEvent, deepEqualWithMockValues, + useLlmobs, MOCK_ANY, MOCK_NUMBER, - MOCK_STRING + MOCK_STRING, + MOCK_OBJECT } diff --git a/packages/dd-trace/test/plugin_manager.spec.js b/packages/dd-trace/test/plugin_manager.spec.js index 16cf108cd1e..7f43b928ccf 100644 --- a/packages/dd-trace/test/plugin_manager.spec.js +++ b/packages/dd-trace/test/plugin_manager.spec.js @@ -16,6 +16,7 @@ describe('Plugin Manager', () => { let Four let Five let Six + let Eight let pm beforeEach(() => { @@ -45,7 +46,11 @@ describe('Plugin Manager', () => { six: class Six extends FakePlugin { static id = 'six' }, - seven: {} + seven: {}, + eight: class Eight extends FakePlugin { + static experimental = true + static id = 'eight' + } } Two = plugins.two @@ -59,6 +64,9 @@ describe('Plugin Manager', () => { Six = plugins.six Six.prototype.configure = sinon.spy() + Eight = plugins.eight + Eight.prototype.configure = sinon.spy() + process.env.DD_TRACE_DISABLED_PLUGINS = 'five,six,seven' PluginManager = proxyquire.noPreserveCache()('../src/plugin_manager', { @@ -75,6 +83,7 @@ describe('Plugin Manager', () => { afterEach(() => { delete process.env.DD_TRACE_DISABLED_PLUGINS + delete process.env.DD_TRACE_EIGHT_ENABLED pm.destroy() }) @@ -265,6 +274,28 @@ describe('Plugin Manager', () => { }) }) + describe('with an experimental plugin', () => { + it('should disable the plugin by default', () => { + pm.configure() + loadChannel.publish({ name: 'eight' }) + expect(Eight.prototype.configure).to.have.been.calledWithMatch({ enabled: false }) + }) + + it('should enable the plugin when configured programmatically', () => { + pm.configure() + pm.configurePlugin('eight') + loadChannel.publish({ name: 'eight' }) + expect(Eight.prototype.configure).to.have.been.calledWithMatch({ enabled: true }) + }) + + it('should enable the plugin when configured with an environment variable', () => { + process.env.DD_TRACE_EIGHT_ENABLED = 'true' + pm.configure() + loadChannel.publish({ name: 'eight' }) + expect(Eight.prototype.configure).to.have.been.calledWithMatch({ enabled: true }) + }) + }) + it('instantiates plugin classes', () => { pm.configure() loadChannel.publish({ name: 'two' }) diff --git a/packages/dd-trace/test/plugins/agent.js b/packages/dd-trace/test/plugins/agent.js index 0d992b10a6a..ed88d26ce47 100644 --- a/packages/dd-trace/test/plugins/agent.js +++ b/packages/dd-trace/test/plugins/agent.js @@ -12,6 +12,7 @@ const { expect } = require('chai') const traceHandlers = new Set() const statsHandlers = new Set() +const llmobsHandlers = new Set() let sockets = [] let agent = null let listener = null @@ -331,6 +332,7 @@ module.exports = { tracer = require('../..') agent = express() agent.use(bodyParser.raw({ limit: Infinity, type: 'application/msgpack' })) + agent.use(bodyParser.text({ limit: Infinity, type: 'application/json' })) agent.use((req, res, next) => { if (req.is('application/msgpack')) { if (!req.body.length) return res.status(200).send() @@ -367,6 +369,14 @@ module.exports = { // EVP proxy endpoint agent.post('/evp_proxy/v2/api/v2/citestcycle', ciVisRequestHandler) + // LLM Observability traces endpoint + agent.post('/evp_proxy/v2/api/v2/llmobs', (req, res) => { + llmobsHandlers.forEach(({ handler }) => { + handler(JSON.parse(req.body)) + }) + res.status(200).send() + }) + // DSM Checkpoint endpoint dsmStats = [] agent.post('/v0.1/pipeline_stats', (req, res) => { @@ -511,12 +521,23 @@ module.exports = { return runCallbackAgainstTraces(callback, options, statsHandlers) }, + /** + * Use a callback handler for LLM Observability traces. + * @param {Function} callback + * @param {Record} options + * @returns + */ + useLlmobsTraces (callback, options) { + return runCallbackAgainstTraces(callback, options, llmobsHandlers) + }, + /** * Unregister any outstanding expectation callbacks. */ reset () { traceHandlers.clear() statsHandlers.clear() + llmobsHandlers.clear() }, /** @@ -540,6 +561,7 @@ module.exports = { agent = null traceHandlers.clear() statsHandlers.clear() + llmobsHandlers.clear() for (const plugin of plugins) { tracer.use(plugin, { enabled: false }) } @@ -552,6 +574,8 @@ module.exports = { this.setAvailableEndpoints(DEFAULT_AVAILABLE_ENDPOINTS) currentIntegrationName = null + tracer.llmobs.disable() + return new Promise((resolve, reject) => { this.server.on('close', () => { this.server = null diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 554a5065709..702584168cd 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -1,4 +1,18 @@ { + "ai": [ + { + "name": "ai", + "versions": ["4.0.2"] + }, + { + "name": "@ai-sdk/openai", + "versions": [">=1.3.23"] + }, + { + "name": "zod", + "versions": [">=3.25.75"] + } + ], "apollo": [ { "name": "@apollo/subgraph", diff --git a/packages/dd-trace/test/plugins/util/ip_extractor.spec.js b/packages/dd-trace/test/plugins/util/ip_extractor.spec.js index 8138a01b353..32995439560 100644 --- a/packages/dd-trace/test/plugins/util/ip_extractor.spec.js +++ b/packages/dd-trace/test/plugins/util/ip_extractor.spec.js @@ -290,4 +290,206 @@ describe('ip extractor', () => { } }).catch(done) }) + + describe('Forwarded header', () => { + it('should detect ip in header \'Forwarded\' in for', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `for=${expectedIp}` + } + }).catch(done) + }) + + it('should detect ip in header \'Forwarded\' in by', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `by=${expectedIp}` + } + }).catch(done) + }) + + it('should detect ipv6 in header \'Forwarded\' in for', (done) => { + const expectedIp = '5a54:f844:006c:b8f1:0e96:9e54:54ac:4a2d' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `for="[${expectedIp}]"` + } + }).catch(done) + }) + + it('should detect ipv6 in header \'Forwarded\' in by', (done) => { + const expectedIp = '5a54:f844:006c:b8f1:0e96:9e54:54ac:4a2d' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `by="[${expectedIp}]"` + } + }).catch(done) + }) + + it('should detect ip in header \'Forwarded\' in by when for is private', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `for=192.168.0.1;by=${expectedIp}` + } + }).catch(done) + }) + + it('should detect ip in header \'Forwarded\' in for when for and by are public', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `by=5.6.7.8;for=${expectedIp}` + } + }).catch(done) + }) + + it('should detect ip in header \'x-client-ip\' when \'Forwarded\' is also set', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + 'x-client-ip': expectedIp, + Forwarded: 'for=5.6.7.8' + } + }).catch(done) + }) + + it('should detect ip in header \'Forwarded\' when \'forwarded-for\' is also set', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + 'forwarded-for': '5.6.7.8', + Forwarded: `for=${expectedIp}` + } + }).catch(done) + }) + + it('should detect ip in header \'Forwarded\' when \'host\' and \'proto\' are also set', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `for=${expectedIp};proto=http;host=testhost` + } + }).catch(done) + }) + + it('should detect ip in header \'Forwarded\' when port is included in the IP', (done) => { + const expectedIp = '1.2.3.4' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `for=${expectedIp}:8080` + } + }).catch(done) + }) + + it('should detect ip in header \'Forwarded\' when port is included in the IPv6', (done) => { + const expectedIp = '5a54:f844:006c:b8f1:0e96:9e54:54ac:4a2d' + controller = function (req) { + const ip = extractIp({}, req) + try { + expect(ip).to.be.equal(expectedIp) + done() + } catch (e) { + done(e) + } + } + axios.get(`http://localhost:${port}/`, { + headers: { + Forwarded: `for=[${expectedIp}]:8080` + } + }).catch(done) + }) + }) }) diff --git a/packages/dd-trace/test/plugins/versions/package.json b/packages/dd-trace/test/plugins/versions/package.json index 16cac01c49e..8fbcb7373bd 100644 --- a/packages/dd-trace/test/plugins/versions/package.json +++ b/packages/dd-trace/test/plugins/versions/package.json @@ -4,46 +4,47 @@ "license": "BSD-3-Clause", "private": true, "dependencies": { + "@ai-sdk/openai": "2.0.1", "@apollo/gateway": "2.11.2", "@apollo/server": "5.0.0", "@apollo/subgraph": "2.11.2", - "@aws-sdk/client-bedrock-runtime": "3.859.0", - "@aws-sdk/client-dynamodb": "3.859.0", - "@aws-sdk/client-kinesis": "3.859.0", - "@aws-sdk/client-lambda": "3.859.0", - "@aws-sdk/client-s3": "3.859.0", - "@aws-sdk/client-sfn": "3.859.0", - "@aws-sdk/client-sns": "3.859.0", - "@aws-sdk/client-sqs": "3.859.0", + "@aws-sdk/client-bedrock-runtime": "3.864.0", + "@aws-sdk/client-dynamodb": "3.864.0", + "@aws-sdk/client-kinesis": "3.864.0", + "@aws-sdk/client-lambda": "3.865.0", + "@aws-sdk/client-s3": "3.864.0", + "@aws-sdk/client-sfn": "3.864.0", + "@aws-sdk/client-sns": "3.864.0", + "@aws-sdk/client-sqs": "3.864.0", "@aws-sdk/node-http-handler": "3.374.0", "@aws-sdk/smithy-client": "3.374.0", "@azure/functions": "4.7.3", "@azure/service-bus": "7.9.5", "@confluentinc/kafka-javascript": "1.4.0", "@cucumber/cucumber": "12.1.0", - "@elastic/elasticsearch": "9.1.0", - "@elastic/transport": "9.1.0", + "@elastic/elasticsearch": "9.1.1", + "@elastic/transport": "9.1.1", "@fastify/cookie": "11.0.2", "@fastify/multipart": "9.0.3", - "@google-cloud/pubsub": "5.1.0", + "@google-cloud/pubsub": "5.2.0", "@google-cloud/vertexai": "1.10.0", "@graphql-tools/executor": "1.4.9", "@grpc/grpc-js": "1.13.4", "@grpc/proto-loader": "0.8.0", "@hapi/boom": "10.0.1", - "@hapi/hapi": "21.4.0", - "@hono/node-server": "1.18.1", + "@hapi/hapi": "21.4.3", + "@hono/node-server": "1.18.2", "@jest/core": "30.0.5", "@jest/globals": "30.0.5", "@jest/reporters": "30.0.5", "@jest/test-sequencer": "30.0.5", "@jest/transform": "30.0.5", "@koa/router": "14.0.0", - "@langchain/anthropic": "0.3.25", + "@langchain/anthropic": "0.3.26", "@langchain/cohere": "0.3.4", - "@langchain/core": "0.3.66", + "@langchain/core": "0.3.70", "@langchain/google-genai": "0.2.16", - "@langchain/openai": "0.6.3", + "@langchain/openai": "0.6.7", "@node-redis/client": "1.0.6", "@opensearch-project/opensearch": "3.5.1", "@opentelemetry/exporter-jaeger": "2.0.1", @@ -52,13 +53,14 @@ "@opentelemetry/instrumentation-http": "0.203.0", "@opentelemetry/sdk-node": "0.203.0", "@playwright/test": "1.54.2", - "@prisma/client": "6.13.0", - "@redis/client": "5.7.0", - "@smithy/smithy-client": "4.4.9", + "@prisma/client": "6.14.0", + "@redis/client": "5.8.1", + "@smithy/smithy-client": "4.4.10", "@vitest/coverage-istanbul": "3.2.4", "@vitest/coverage-v8": "3.2.4", "@vitest/runner": "3.2.4", - "aerospike": "6.2.0", + "aerospike": "6.3.0", + "ai": "5.0.2", "amqp10": "3.6.0", "amqplib": "0.10.8", "apollo-server-core": "3.13.0", @@ -78,7 +80,7 @@ "cookie": "1.0.2", "cookie-parser": "1.4.7", "couchbase": "4.5.0", - "cypress": "14.5.3", + "cypress": "14.5.4", "cypress-fail-fast": "7.1.1", "dd-trace-api": "1.0.0", "ejs": "3.1.10", @@ -86,7 +88,7 @@ "express": "5.1.0", "express-mongo-sanitize": "2.2.0", "express-session": "1.18.2", - "fastify": "5.4.0", + "fastify": "5.5.0", "find-my-way": "9.3.0", "fs": "0.0.2", "generic-pool": "3.9.0", @@ -97,7 +99,7 @@ "graphql-yoga": "5.15.1", "handlebars": "4.7.8", "hapi": "18.1.0", - "hono": "4.8.12", + "hono": "4.9.1", "ioredis": "5.7.0", "iovalkey": "0.3.3", "jest": "30.0.5", @@ -130,18 +132,18 @@ "moleculer": "0.14.35", "mongodb": "6.18.0", "mongodb-core": "3.2.7", - "mongoose": "8.17.0", + "mongoose": "8.17.1", "mquery": "5.0.0", "multer": "2.0.2", "mysql": "2.18.1", "mysql2": "3.14.3", - "next": "15.4.5", - "nock": "14.0.8", + "next": "15.4.6", + "nock": "14.0.10", "node-serialize": "0.0.4", "npm": "11.5.2", "nyc": "17.1.0", "office-addin-mock": "3.0.3", - "openai": "5.11.0", + "openai": "5.12.2", "oracledb": "6.9.0", "passport": "0.7.0", "passport-http": "0.3.0", @@ -150,7 +152,7 @@ "pg-cursor": "2.15.3", "pg-native": "3.5.2", "pg-query-stream": "4.10.3", - "pino": "9.7.0", + "pino": "9.9.0", "pino-pretty": "13.1.1", "playwright": "1.54.2", "playwright-core": "1.54.2", @@ -162,12 +164,12 @@ "q": "2.0.3", "react": "19.1.1", "react-dom": "19.1.1", - "redis": "5.7.0", + "redis": "5.8.1", "request": "2.88.2", "restify": "11.1.0", "rhea": "3.0.4", "router": "2.2.0", - "selenium-webdriver": "4.34.0", + "selenium-webdriver": "4.35.0", "sequelize": "6.37.7", "sharedb": "5.2.2", "sinon": "21.0.0", @@ -180,6 +182,7 @@ "winston": "3.17.0", "workerpool": "9.3.3", "ws": "8.18.3", - "yarn": "1.22.22" + "yarn": "1.22.22", + "zod": "4.0.14" } } diff --git a/packages/dd-trace/test/profiling/profilers/events.spec.js b/packages/dd-trace/test/profiling/profilers/events.spec.js index 199c6dd85d7..bb648603220 100644 --- a/packages/dd-trace/test/profiling/profilers/events.spec.js +++ b/packages/dd-trace/test/profiling/profilers/events.spec.js @@ -17,7 +17,7 @@ describe('profilers/events', () => { // Set up a mock span to simulate tracing context const span = { context: () => ({ - toSpanId: () => '1234', + toBigIntSpanId: () => 1234n, _trace: { started: [span] } diff --git a/packages/dd-trace/test/profiling/profilers/poisson.spec.js b/packages/dd-trace/test/profiling/profilers/poisson.spec.js index ee412a5fc7b..89a5055115c 100644 --- a/packages/dd-trace/test/profiling/profilers/poisson.spec.js +++ b/packages/dd-trace/test/profiling/profilers/poisson.spec.js @@ -65,8 +65,10 @@ describe('PoissonProcessSamplingFilter', () => { resetInterval: 20, now: constantNow }) - const event = { startTime: 0, duration: 100 } - assert.doesNotThrow(() => filter.filter(event)) + assert.strictEqual(callCount, 1) + const event = { startTime: 0, duration: Number.POSITIVE_INFINITY } + // Make sure that filtering events does not throw and calls now() once + filter.filter(event) assert.strictEqual(callCount, 2) }) diff --git a/packages/dd-trace/test/setup/core.js b/packages/dd-trace/test/setup/core.js index 6dc88017d8d..714fd3da7f0 100644 --- a/packages/dd-trace/test/setup/core.js +++ b/packages/dd-trace/test/setup/core.js @@ -4,7 +4,6 @@ const sinon = require('sinon') const chai = require('chai') const sinonChai = require('sinon-chai') const proxyquire = require('../proxyquire') -const { NODE_MAJOR } = require('../../../../version') chai.use(sinonChai) chai.use(require('../asserts/profile')) @@ -26,29 +25,3 @@ if (/^v\d+\.x$/.test(process.env.GITHUB_BASE_REF || '')) { process.env.DD_INJECTION_ENABLED = 'true' process.env.DD_INJECT_FORCE = 'true' } - -// TODO(bengl): remove this block once we can properly support Node.js 24 without it -if (NODE_MAJOR >= 24 && !process.env.OPTIONS_OVERRIDE) { - const childProcess = require('child_process') - const { exec, fork } = childProcess - - function addAsyncContextFrame (fn, thisArg, args) { - const opts = args[1] - if (opts) { - const env = opts.env ||= {} - env.NODE_OPTIONS ||= '' - if (!env.NODE_OPTIONS.includes('--no-async-context-frame')) { - env.NODE_OPTIONS += ' --no-async-context-frame' - } - } - return fn.apply(thisArg, args) - } - - childProcess.exec = function () { - return addAsyncContextFrame(exec, this, arguments) - } - - childProcess.fork = function () { - return addAsyncContextFrame(fork, this, arguments) - } -} diff --git a/packages/dd-trace/test/setup/mocha.js b/packages/dd-trace/test/setup/mocha.js index ef684349b92..469adb58b4c 100644 --- a/packages/dd-trace/test/setup/mocha.js +++ b/packages/dd-trace/test/setup/mocha.js @@ -16,9 +16,6 @@ const { getInstrumentation } = require('./helpers/load-inst') const NODE_PATH_SEP = platform() === 'win32' ? ';' : ':' -// TODO: Remove global -global.withVersions = withVersions - exports.withVersions = withVersions exports.withExports = withExports exports.withNamingSchema = withNamingSchema diff --git a/scripts/flakiness.mjs b/scripts/flakiness.mjs index 7933edd5dd7..f2f417b2fb6 100644 --- a/scripts/flakiness.mjs +++ b/scripts/flakiness.mjs @@ -1,9 +1,11 @@ /* eslint-disable no-console */ +import { writeFileSync } from 'fs' import { Octokit } from 'octokit' const { BRANCH, + CI, DAYS = '1', OCCURRENCES = '1', UNTIL @@ -111,7 +113,7 @@ await Promise.all(workflows.map(w => checkWorkflowRuns(w))) // TODO: Report this somewhere useful instead. const dateRange = startDate === endDate ? `on ${endDate}` : `from ${startDate} to ${endDate}` -const logString = `jobs with at least ${OCCURRENCES} occurrences seen ${dateRange} (UTC)*` +const logString = `jobs with at least ${OCCURRENCES} occurrences seen ${dateRange} (UTC)` if (Object.keys(flaky).length === 0) { console.log(`*No flaky ${logString}`) @@ -120,21 +122,48 @@ if (Object.keys(flaky).length === 0) { const pipelineSuccessRate = +((workflowSuccessRate / 100) ** workflows.length * 100).toFixed(1) const pipelineBadge = pipelineSuccessRate >= 85 ? '🟢' : pipelineSuccessRate >= 75 ? '🟡' : '🔴' - console.log(`*Flaky ${logString}`) + let markdown = '' + let slack = '' + + markdown += `**Flaky ${logString}**\n` + slack += `*Flaky ${logString}*\\n` + for (const [workflow, jobs] of Object.entries(flaky).sort()) { if (!reported.has(workflow)) continue - console.log(`* ${workflow}`) + + markdown += `* ${workflow}\n` + slack += ` ● ${workflow}\\n` + for (const [job, urls] of Object.entries(jobs).sort()) { if (urls.length < OCCURRENCES) continue // Padding is needed because Slack doesn't show single digits as links. - const links = urls.map((url, idx) => `[${String(idx + 1).padStart(2, '0')}](${url})`) + const markdownLinks = urls.map((url, idx) => `[${String(idx + 1).padStart(2, '0')}](${url})`) + const slackLinks = urls.map((url, idx) => `<${url}|${String(idx + 1).padStart(2, '0')}>`) const runsBadge = urls.length >= 3 ? ' 🔴' : urls.length === 2 ? ' 🟡' : '' - console.log(` * ${job} (${links.join(', ')})${runsBadge}`) + markdown += ` * ${job} (${markdownLinks.join(', ')})${runsBadge}\n` + slack += ` ○ ${job} (${slackLinks.join(', ')})${runsBadge}\\n` } } - console.log('*Flakiness stats*') - console.log(`* Total runs: ${totalCount}`) - console.log(`* Flaky runs: ${flakeCount}`) - console.log(`* Workflow success rate: ${workflowSuccessRate}%`) - console.log(`* Pipeline success rate (approx): ${pipelineSuccessRate}% ${pipelineBadge}`) + + markdown += '\n' + markdown += '**Flakiness stats**\n' + markdown += `* Total runs: ${totalCount}\n` + markdown += `* Flaky runs: ${flakeCount}\n` + markdown += `* Workflow success rate: ${workflowSuccessRate}%\n` + markdown += `* Pipeline success rate (approx): ${pipelineSuccessRate}% ${pipelineBadge}` + + slack += '\\n' + slack += '*Flakiness stats*\\n' + slack += ` ● Total runs: ${totalCount}\\n` + slack += ` ● Flaky runs: ${flakeCount}\\n` + slack += ` ● Workflow success rate: ${workflowSuccessRate}%\\n` + slack += ` ● Pipeline success rate (approx): ${pipelineSuccessRate}% ${pipelineBadge}` + + console.log(markdown) + + // TODO: Make this an option instead. + if (CI) { + writeFileSync('flakiness.md', markdown) + writeFileSync('flakiness.txt', slack) + } } diff --git a/yarn.lock b/yarn.lock index fa3959202be..82483ccdf23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -217,10 +217,10 @@ resolved "https://registry.yarnpkg.com/@datadog/libdatadog/-/libdatadog-0.7.0.tgz#81e07d3040c628892db697ccd01ae3c4d2a76315" integrity sha512-VVZLspzQcfEU47gmGCVoRkngn7RgFRR4CHjw4YaX8eWT+xz4Q4l6PvA45b7CMk9nlt3MNN5MtGdYttYMIpo6Sg== -"@datadog/native-appsec@10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-10.0.1.tgz#220bd855971ebe3c517915a6db4caec50545b128" - integrity sha512-v60KOnQOAn6lcrFB8eHXfW5H227mk5tuj0CCqlk1FCZ5UxW8jjvXYV3cVjlQvSkwk55BN9jh6xa99QaX5WkqmQ== +"@datadog/native-appsec@10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-10.1.0.tgz#60a388b1e7055b39d67d070219f2c14fc239b9c3" + integrity sha512-IKV9L4MvQxrT6GK0k5n9oOWw34gsGaiHW/03J1DOEu1crUqXcSWYJVOrGnRwz6XPXf6LDtAvmR+AU1QwDcDsww== dependencies: node-gyp-build "^3.9.0" @@ -286,15 +286,15 @@ debug "^4.3.1" minimatch "^3.1.2" -"@eslint/config-helpers@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.0.tgz#3e09a90dfb87e0005c7694791e58e97077271286" - integrity sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw== +"@eslint/config-helpers@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617" + integrity sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== -"@eslint/core@^0.15.0", "@eslint/core@^0.15.1": - version "0.15.1" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.1.tgz#d530d44209cbfe2f82ef86d6ba08760196dd3b60" - integrity sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA== +"@eslint/core@^0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" + integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== dependencies: "@types/json-schema" "^7.0.15" @@ -313,22 +313,22 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.32.0", "@eslint/js@^9.29.0": - version "9.32.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.32.0.tgz#a02916f58bd587ea276876cb051b579a3d75d091" - integrity sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg== +"@eslint/js@9.33.0", "@eslint/js@^9.29.0": + version "9.33.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.33.0.tgz#475c92fdddab59b8b8cab960e3de2564a44bf368" + integrity sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A== "@eslint/object-schema@^2.1.6": version "2.1.6" resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== -"@eslint/plugin-kit@^0.3.3", "@eslint/plugin-kit@^0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz#c6b9f165e94bf4d9fdd493f1c028a94aaf5fc1cc" - integrity sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw== +"@eslint/plugin-kit@^0.3.3", "@eslint/plugin-kit@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" + integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== dependencies: - "@eslint/core" "^0.15.1" + "@eslint/core" "^0.15.2" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -780,12 +780,12 @@ integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== "@stylistic/eslint-plugin@^5.0.0": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-5.2.2.tgz#a1cb24f17263e1dcb2d5c94069dc3ae7af1eb9ce" - integrity sha512-bE2DUjruqXlHYP3Q2Gpqiuj2bHq7/88FnuaS0FjeGGLCy+X6a07bGVuwtiOYnPSLHR6jmx5Bwdv+j7l8H+G97A== + version "5.2.3" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-5.2.3.tgz#f2be5d25e768f5ef4bb72d339bb71c500accef61" + integrity sha512-oY7GVkJGVMI5benlBDCaRrSC1qPasafyv5dOBLLv5MTilMGnErKhO6ziEfodDDIZbo5QxPUNW360VudJOFODMw== dependencies: "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/types" "^8.37.0" + "@typescript-eslint/types" "^8.38.0" eslint-visitor-keys "^4.2.1" espree "^10.4.0" estraverse "^5.3.0" @@ -819,9 +819,9 @@ undici-types "~6.20.0" "@types/node@^18.19.106": - version "18.19.121" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.121.tgz#c50d353ea2d1fb1261a8bbd0bf2850306f5af2b3" - integrity sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ== + version "18.19.122" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.122.tgz#e7358f8df7cc3f14e860198e1ca4dc2ed9a7de06" + integrity sha512-yzegtT82dwTNEe/9y+CM8cgb42WrUfMMCg2QqSddzO1J6uPmBD7qKCZ7dOHZP2Yrpm/kb0eqdNMn2MUyEiqBmA== dependencies: undici-types "~5.26.4" @@ -849,10 +849,10 @@ resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.2.tgz#efaf9e991a7390dc081a0b679185979a83a9639a" integrity sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw== -"@typescript-eslint/types@^8.37.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.38.0.tgz#297351c994976b93c82ac0f0e206c8143aa82529" - integrity sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw== +"@typescript-eslint/types@^8.38.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.39.0.tgz#80f010b7169d434a91cd0529d70a528dbc9c99c6" + integrity sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg== "@yarnpkg/lockfile@^1.1.0": version "1.1.0" @@ -1996,18 +1996,18 @@ eslint-visitor-keys@^4.2.1: integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== eslint@^9.29.0: - version "9.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.32.0.tgz#4ea28df4a8dbc454e1251e0f3aed4bcf4ce50a47" - integrity sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg== + version "9.33.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.33.0.tgz#cc186b3d9eb0e914539953d6a178a5b413997b73" + integrity sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" "@eslint/config-array" "^0.21.0" - "@eslint/config-helpers" "^0.3.0" - "@eslint/core" "^0.15.0" + "@eslint/config-helpers" "^0.3.1" + "@eslint/core" "^0.15.2" "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.32.0" - "@eslint/plugin-kit" "^0.3.4" + "@eslint/js" "9.33.0" + "@eslint/plugin-kit" "^0.3.5" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/retry" "^0.4.2" @@ -4620,9 +4620,9 @@ test-exclude@^6.0.0: minimatch "^3.0.4" tiktoken@^1.0.21: - version "1.0.21" - resolved "https://registry.yarnpkg.com/tiktoken/-/tiktoken-1.0.21.tgz#434f4c67ccda114cdfb19a180b93d9be8bc396be" - integrity sha512-/kqtlepLMptX0OgbYD9aMYbM7EFrMZCL7EoHM8Psmg2FuhXoo/bH64KqOiZGGwa6oS9TPdSEDKBnV2LuB8+5vQ== + version "1.0.22" + resolved "https://registry.yarnpkg.com/tiktoken/-/tiktoken-1.0.22.tgz#a6c674839228bb88f32dfe646dff47193762f7d3" + integrity sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA== tlhunter-sorted-set@^0.1.0: version "0.1.0" @@ -5046,9 +5046,9 @@ yaml@^1.10.2: integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yaml@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6" - integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ== + version "2.8.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" + integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== yargs-parser@^18.1.2: version "18.1.3"