Compare commits

...
63 Commits
Author SHA1 Message Date
Max Deichmann 60bf74698a chore: release v2.16.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-04-05 21:21:21 +02:00
Max DeichmannandGitHub 5ac7dc969e fix: do not send io for traces on trpc.all route (#1514) 2024-04-05 21:16:26 +02:00
Marc KlingenandGitHub 3b152eda21 fix: updates to openapi spec, broken due to monorepo transition (#1603) 2024-04-05 21:16:02 +02:00
Max Deichmann bfde018060 chore: release v2.16.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-04-05 18:39:10 +02:00
Max DeichmannandGitHub d83bc01b22 chore: upgrade fern (#1600) 2024-04-05 12:33:51 +02:00
Marc Klingen feb811ccbb fix: csp header script-src 2024-04-05 11:49:45 +02:00
dependabot[bot]andGitHub 5a2f478613 chore(deps): bump the patches group with 8 updates (#1596) 2024-04-05 06:39:19 +00:00
dependabot[bot]andGitHub 29c726499e chore(deps): bump the prisma group with 2 updates (#1595) 2024-04-05 06:21:02 +00:00
dependabot[bot]andGitHub c15435126d chore(deps): bump lucide-react from 0.330.0 to 0.364.0 (#1598) 2024-04-05 08:12:19 +02:00
Marc Klingen 343fdf6785 chore: release v2.16.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-04-05 01:30:55 +02:00
Marc Klingen ada8f639f1 chore: backfill source_trace_id for existing source_observation_id on dataset items 2024-04-05 01:29:08 +02:00
Marc KlingenandGitHub 913c5e3b3b feat(datasets): add description to datasets&runs, add datasetName to GET runs/[runName] response (#1594) 2024-04-05 01:11:52 +02:00
Marc KlingenandGitHub 035b8a76df ci: fix playwright e2e tests (#1599) 2024-04-05 00:50:44 +02:00
Marc KlingenandGitHub 14717f56b2 feat(datasets): create dataset item based on trace (#1592) 2024-04-04 22:59:56 +02:00
Marc KlingenandGitHub bef70d0c2e docs: descriptions for pagination api args (#1591) 2024-04-04 22:29:49 +02:00
Marc KlingenandGitHub 5aaa509954 feat(api): add GET /datasets (#1590) 2024-04-04 22:27:48 +02:00
Marc Klingen 3026ee4b27 chore: release v2.15.5
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-04-04 21:08:24 +02:00
Marc KlingenandGitHub 7e2dd0a42b feat(datasets): input of dataset items can be nullable (#1523) 2024-04-04 21:00:13 +02:00
Max DeichmannandGitHub 7c4092a8c5 chore: upgrade fern generator (#1585) 2024-04-04 11:56:32 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ae3464856c chore(deps-dev): bump the patches group with 1 update (#1576)
Bumps the patches group with 1 update: [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom).


Updates `@types/react-dom` from 18.2.23 to 18.2.24
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 01:12:10 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4501244544 chore(deps): bump @aws-sdk/lib-storage from 3.536.0 to 3.540.0 (#1580)
Bumps [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) from 3.536.0 to 3.540.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.540.0/lib/lib-storage)

---
updated-dependencies:
- dependency-name: "@aws-sdk/lib-storage"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 03:02:11 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8941ee0939 chore(deps-dev): bump @vercel/style-guide from 5.2.0 to 6.0.0 (#1578)
Bumps [@vercel/style-guide](https://github.com/vercel/style-guide) from 5.2.0 to 6.0.0.
- [Release notes](https://github.com/vercel/style-guide/releases)
- [Changelog](https://github.com/vercel/style-guide/blob/canary/.releaserc.js)
- [Commits](https://github.com/vercel/style-guide/compare/v5.2.0...v6.0.0)

---
updated-dependencies:
- dependency-name: "@vercel/style-guide"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 00:38:06 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f0b8fc6aa6 chore(deps): bump @aws-sdk/s3-request-presigner from 3.536.0 to 3.540.0 (#1577)
Bumps [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-request-presigner) from 3.536.0 to 3.540.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-request-presigner/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.540.0/packages/s3-request-presigner)

---
updated-dependencies:
- dependency-name: "@aws-sdk/s3-request-presigner"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 22:29:04 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9468c8ca93 chore(deps): bump the npm_and_yarn group with 1 update (#1575)
Bumps the npm_and_yarn group with 1 update: [express](https://github.com/expressjs/express).


Updates `express` from 4.18.3 to 4.19.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.3...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:production
  dependency-group: npm_and_yarn-security-group
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 22:15:44 +00:00
Marc Klingen cfd127c4db chore: release v2.15.4
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-04-03 23:46:54 +02:00
Max DeichmannandGitHub 98a0cc29c4 chore: downgrade express (#1574) 2024-04-03 19:21:06 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
27b3771861 chore(deps): bump the patches group with 22 updates (#1568)
Bumps the patches group with 22 updates:

| Package | From | To |
| --- | --- | --- |
| [@heroicons/react](https://github.com/tailwindlabs/heroicons) | `2.1.1` | `2.1.3` |
| [@langchain/anthropic](https://github.com/langchain-ai/langchainjs) | `0.1.8` | `0.1.9` |
| [@langchain/core](https://github.com/langchain-ai/langchainjs) | `0.1.49` | `0.1.53` |
| [@langchain/openai](https://github.com/langchain-ai/langchainjs) | `0.0.23` | `0.0.25` |
| [@react-email/components](https://github.com/resend/react-email/tree/HEAD/packages/components) | `0.0.14` | `0.0.16` |
| [ai](https://github.com/vercel/ai) | `3.0.13` | `3.0.17` |
| [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) | `3.36.0` | `3.36.1` |
| [langchain](https://github.com/langchain-ai/langchainjs) | `0.1.28` | `0.1.30` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `6.9.12` | `6.9.13` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.51.1` | `7.51.2` |
| [sonner](https://github.com/emilkowalski/sonner) | `1.4.3` | `1.4.41` |
| [@testing-library/react](https://github.com/testing-library/react-testing-library) | `14.2.1` | `14.2.2` |
| [@types/eslint](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/eslint) | `8.56.5` | `8.56.7` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.2.67` | `18.2.74` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `18.2.22` | `18.2.23` |
| [autoprefixer](https://github.com/postcss/autoprefixer) | `10.4.18` | `10.4.19` |
| [postcss](https://github.com/postcss/postcss) | `8.4.36` | `8.4.38` |
| [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) | `0.5.12` | `0.5.13` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss) | `3.4.1` | `3.4.3` |
| [typescript](https://github.com/Microsoft/TypeScript) | `5.4.2` | `5.4.3` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.11.3` | `8.11.5` |
| [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) | `8.11.2` | `8.11.4` |
| [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg) | `8.11.2` | `8.11.4` |


Updates `@heroicons/react` from 2.1.1 to 2.1.3
- [Release notes](https://github.com/tailwindlabs/heroicons/releases)
- [Changelog](https://github.com/tailwindlabs/heroicons/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/heroicons/compare/v2.1.1...v2.1.3)

Updates `@langchain/anthropic` from 0.1.8 to 0.1.9
- [Release notes](https://github.com/langchain-ai/langchainjs/releases)
- [Changelog](https://github.com/langchain-ai/langchainjs/blob/main/release_workspace.js)
- [Commits](https://github.com/langchain-ai/langchainjs/compare/0.1.8...0.1.9)

Updates `@langchain/core` from 0.1.49 to 0.1.53
- [Release notes](https://github.com/langchain-ai/langchainjs/releases)
- [Changelog](https://github.com/langchain-ai/langchainjs/blob/main/release_workspace.js)
- [Commits](https://github.com/langchain-ai/langchainjs/commits)

Updates `@langchain/openai` from 0.0.23 to 0.0.25
- [Release notes](https://github.com/langchain-ai/langchainjs/releases)
- [Changelog](https://github.com/langchain-ai/langchainjs/blob/main/release_workspace.js)
- [Commits](https://github.com/langchain-ai/langchainjs/commits/0.0.25)

Updates `@react-email/components` from 0.0.14 to 0.0.16
- [Release notes](https://github.com/resend/react-email/releases)
- [Commits](https://github.com/resend/react-email/commits/@react-email/components@0.0.16/packages/components)

Updates `ai` from 3.0.13 to 3.0.17
- [Release notes](https://github.com/vercel/ai/releases)
- [Commits](https://github.com/vercel/ai/compare/ai@3.0.13...ai@3.0.17)

Updates `core-js` from 3.36.0 to 3.36.1
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.36.1/packages/core-js)

Updates `langchain` from 0.1.28 to 0.1.30
- [Release notes](https://github.com/langchain-ai/langchainjs/releases)
- [Changelog](https://github.com/langchain-ai/langchainjs/blob/main/release_workspace.js)
- [Commits](https://github.com/langchain-ai/langchainjs/compare/0.1.28...0.1.30)

Updates `nodemailer` from 6.9.12 to 6.9.13
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.12...v6.9.13)

Updates `react-hook-form` from 7.51.1 to 7.51.2
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.51.1...v7.51.2)

Updates `sonner` from 1.4.3 to 1.4.41
- [Release notes](https://github.com/emilkowalski/sonner/releases)
- [Commits](https://github.com/emilkowalski/sonner/compare/v1.4.3...v.1.4.41)

Updates `@testing-library/react` from 14.2.1 to 14.2.2
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v14.2.1...v14.2.2)

Updates `@types/eslint` from 8.56.5 to 8.56.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/eslint)

Updates `@types/react` from 18.2.67 to 18.2.74
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 18.2.22 to 18.2.23
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `autoprefixer` from 10.4.18 to 10.4.19
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.4.18...10.4.19)

Updates `postcss` from 8.4.36 to 8.4.38
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.36...8.4.38)

Updates `prettier-plugin-tailwindcss` from 0.5.12 to 0.5.13
- [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.5.12...v0.5.13)

Updates `tailwindcss` from 3.4.1 to 3.4.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.3/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.1...v3.4.3)

Updates `typescript` from 5.4.2 to 5.4.3
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.2...v5.4.3)

Updates `pg` from 8.11.3 to 8.11.5
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.11.5/packages/pg)

Updates `@types/pg` from 8.11.2 to 8.11.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

Updates `@types/pg` from 8.11.2 to 8.11.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

---
updated-dependencies:
- dependency-name: "@heroicons/react"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@langchain/anthropic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@langchain/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@langchain/openai"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@react-email/components"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: ai
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: core-js
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: langchain
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: nodemailer
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: sonner
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@types/eslint"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: autoprefixer
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: postcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: prettier-plugin-tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: pg
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@types/pg"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@types/pg"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 15:54:16 +02:00
Marc KlingenandGitHub bb2108e858 feat(datasets): connect run items with traces in addition to observations (#1502) 2024-04-03 15:52:48 +02:00
Marc KlingenandGitHub 268160b214 chore: docker compose instead of docker-compose (#1571) 2024-04-03 15:35:53 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9620fa8f63 chore(deps): bump the prisma group with 2 updates (#1560)
Bumps the prisma group with 2 updates: [prisma](https://github.com/prisma/prisma/tree/HEAD/packages/cli) and [@prisma/client](https://github.com/prisma/prisma/tree/HEAD/packages/client).


Updates `prisma` from 5.11.0 to 5.12.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.12.0/packages/cli)

Updates `@prisma/client` from 5.11.0 to 5.12.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.12.0/packages/client)

---
updated-dependencies:
- dependency-name: prisma
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prisma
- dependency-name: "@prisma/client"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prisma
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 15:12:09 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1cab7db5e8 chore(deps): bump the next group with 2 updates (#1561)
Bumps the next group with 2 updates: [next](https://github.com/vercel/next.js) and [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next).


Updates `next` from 14.1.3 to 14.1.4
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.1.3...v14.1.4)

Updates `eslint-config-next` from 14.1.3 to 14.1.4
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v14.1.4/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: next
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: next
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 12:49:16 +00:00
Marc Klingen 779ea774d0 chore: automatically rebase dependabot PRs 2024-04-03 14:39:40 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e746188122 chore(deps): bump react18-json-view from 0.2.8-canary.4 to 0.2.8-canary.6 (#1563)
chore(deps): bump react18-json-view

Bumps [react18-json-view](https://github.com/YYsuni/react18-json-view) from 0.2.8-canary.4 to 0.2.8-canary.6.
- [Release notes](https://github.com/YYsuni/react18-json-view/releases)
- [Commits](https://github.com/YYsuni/react18-json-view/commits)

---
updated-dependencies:
- dependency-name: react18-json-view
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 12:04:01 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d5ccc0fc1b chore(deps): bump the sentry group with 4 updates (#1559)
Bumps the sentry group with 4 updates: [@sentry/nextjs](https://github.com/getsentry/sentry-javascript), [@sentry/node](https://github.com/getsentry/sentry-javascript), [@sentry/profiling-node](https://github.com/getsentry/sentry-javascript) and [@sentry/types](https://github.com/getsentry/sentry-javascript).


Updates `@sentry/nextjs` from 7.107.0 to 7.109.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.109.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.107.0...7.109.0)

Updates `@sentry/node` from 7.107.0 to 7.109.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.109.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.107.0...7.109.0)

Updates `@sentry/profiling-node` from 7.107.0 to 7.109.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.109.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.107.0...7.109.0)

Updates `@sentry/types` from 7.107.0 to 7.109.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.109.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.107.0...7.109.0)

---
updated-dependencies:
- dependency-name: "@sentry/nextjs"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
- dependency-name: "@sentry/profiling-node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
- dependency-name: "@sentry/types"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 13:58:04 +02:00
Max DeichmannandGitHub 14a1764d81 refactor: reduce sentry sampling (#1564) 2024-04-03 06:45:49 +02:00
Marc Klingen 8adef3a4b3 docs: add new integrations to contributing 2024-04-02 17:54:41 +02:00
Marc KlingenandGitHub fd231782d9 chore: upgrade express (#1558) 2024-04-02 17:33:20 +02:00
Marc Klingen 2342ae1bd1 chore: fix dependabot 2024-04-02 17:24:20 +02:00
Marc KlingenandGitHub f90e3da4a9 chore: upgrade deps (#1556) 2024-04-02 17:10:07 +02:00
Marc Klingen 23fa8dd19c chore: release v2.15.3
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-04-02 12:56:57 +02:00
Marc KlingenandGitHub 1007507716 feat(sso): add Auth0 (#1554) 2024-04-02 12:51:50 +02:00
Marc Klingen acf68a13cc fix: stripe csp header 2024-04-02 12:36:35 +02:00
Marc Klingen 17e8249913 chore: release v2.15.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-04-02 12:20:43 +02:00
Marc KlingenandGitHub 0947acce66 feat(auth): add Okta (#1534) 2024-04-02 12:16:36 +02:00
Marc Klingen 8dc5087263 fix(security): allow stripe.com script-src 2024-04-02 12:03:57 +02:00
Richard KrümmelandGitHub 685abd6575 fix(ui): responsive layout of model latency chart (#1553) 2024-04-02 11:52:08 +02:00
Marc Klingen a3fbdee0bd chore: update csp script-src 2024-04-02 10:10:43 +02:00
Marc Klingen 6a24a50892 chore: release v2.15.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-03-31 14:38:45 +02:00
Marc KlingenandGitHub 9be6ffb8ff feat(security): optional enforcement of https via LANGFUSE_CSP_ENFORCE_HTTPS (#1550) 2024-03-31 14:38:18 +02:00
Marc Klingen 938219203e chore: release v2.15.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-03-29 21:49:49 +01:00
Marc KlingenandGitHub 036736d805 chore(api): casing of fromTimestamp param (#1547) 2024-03-29 21:49:04 +01:00
Marc KlingenandGitHub e6aeeff357 feat(api): add fromStartTime param to GET /observations (#1546) 2024-03-29 21:48:19 +01:00
Marc KlingenandGitHub 8799a550b4 feat(api): add from_timestamp to GET /scores (#1545) 2024-03-29 21:37:20 +01:00
Marc KlingenandGitHub 4c4ab027d9 feat(api): add from_timestamp param to get /traces (#1544) 2024-03-29 21:31:29 +01:00
Marc KlingenandGitHub af4106d463 fix(csp): add unsafe-eval to script-src in dev (#1543) 2024-03-29 21:16:13 +01:00
Marc KlingenandGitHub 08ea1807b3 fix(api): totalItems if params are empty strings (#1542) 2024-03-29 21:06:13 +01:00
Marc Klingen a2698664d0 chore: release v2.14.6
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-03-28 22:48:15 +01:00
Marc KlingenandGitHub 096abd4b12 chore(security): add security headers (#1532) 2024-03-28 21:47:03 +01:00
Marc Klingen 04f9a33f64 chore: add codeowners 2024-03-28 15:53:04 +01:00
Max Deichmann c9116d5a29 chore: release v2.14.5
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-03-28 14:11:56 +01:00
Max DeichmannandGitHub 8cfafe1e61 fix: handle nullable values in scores table (#1531) 2024-03-28 12:56:19 +00:00
Marc KlingenandGitHub 6d08720436 fix(ui): refresh turnstile via cdata instead of ref (#1530) 2024-03-28 12:34:37 +00:00
Marc KlingenandGitHub d008a40788 fix(ui): user lastEvent in case of empty traces (#1527) 2024-03-28 09:55:11 +01:00
89 changed files with 2513 additions and 6547 deletions
+11
View File
@@ -20,6 +20,9 @@ NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="secret"
SALT="salt"
# Use CSP headers to enforce HTTPS, optional
LANGFUSE_CSP_ENFORCE_HTTPS="true"
# Docker only, optional
# PORT=3000
# HOSTNAME=localhost
@@ -46,6 +49,14 @@ SALT="salt"
# AUTH_AZURE_AD_CLIENT_SECRET=
# AUTH_AZURE_AD_TENANT_ID=
# AUTH_AZURE_ALLOW_ACCOUNT_LINKING=false
# AUTH_OKTA_CLIENT_ID=
# AUTH_OKTA_CLIENT_SECRET=
# AUTH_OKTA_ISSUER=
# AUTH_OKTA_ALLOW_ACCOUNT_LINKING=false
# AUTH_AUTH0_CLIENT_ID=
# AUTH_AUTH0_CLIENT_SECRET=
# AUTH_AUTH0_ISSUER=
# AUTH_AUTH0_ALLOW_ACCOUNT_LINKING=false
# Transactional email, optional
# Defines the email address to use as the from address.
+1
View File
@@ -0,0 +1 @@
* @langfuse/founders
+2 -29
View File
@@ -6,37 +6,10 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/worker" # Location of package manifests
directory: "/" # Location of package manifests
schedule:
interval: "daily"
rebase-strategy: "disabled" # use dependabot-rebase-stale
commit-message:
prefix: chore
prefix-development: chore
include: scope
ignore:
- dependency-name: "@types/node"
- dependency-name: "@trpc/*"
groups:
sentry:
patterns:
- "@sentry/*"
prisma:
patterns:
- "prisma"
- "@prisma/*"
next:
patterns:
- "eslint-config-next"
- "next"
patches:
update-types:
- "patch"
- package-ecosystem: npm
directory: "/web" # Location of package manifests
schedule:
interval: "daily"
rebase-strategy: "disabled" # use dependabot-rebase-stale
versioning-strategy: "increase"
commit-message:
prefix: chore
prefix-development: chore
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v3
- name: Start containers
run: docker-compose -f "docker-compose.yml" up -d --build
run: docker compose -f "docker-compose.yml" up -d --build
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.4
+3 -5
View File
@@ -87,7 +87,7 @@ jobs:
- name: Run + migrate
run: |
docker-compose -f docker-compose.dev.yml up -d
docker compose -f docker-compose.dev.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
- name: Seed DB
@@ -128,7 +128,7 @@ jobs:
- name: Run + migrate
run: |
docker-compose -f docker-compose.dev.yml up -d
docker compose -f docker-compose.dev.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
- name: Seed DB
@@ -140,9 +140,7 @@ jobs:
run: pnpm run build
- name: Install playwright
run: |
npm add --global playwright
pnpm exec playwright install
run: pnpm --filter=web exec playwright install --with-deps
- name: Run e2e tests
run: pnpm --filter=web run test:e2e
+3 -1
View File
@@ -55,8 +55,10 @@ A good first step is to search for open [issues](https://github.com/langfuse/lan
flowchart TB
subgraph s4["Clients"]
subgraph s2["langfuse/langfuse-python"]
Python["Python SDK"]
Python["Python low-level SDK"]
Decorator["observe() decorator"] -->|extends| Python
OAI["OpenAI drop-in replacement"] -->|extends| Python
Llamaindex["LlamaIndex Integration"] -->|extends| Python
LCPYTHON["Langchain Python Integration"] -->|extends| Python
Langflow -->|uses| LCPYTHON
LiteLLM -->|uses| Python
+21 -3
View File
@@ -122,21 +122,36 @@ types:
observationId: optional<string>
timestamp: datetime
comment: optional<string>
Dataset:
DatasetCore:
properties:
id: string
name: string
description: optional<string>
projectId: string
createdAt: datetime
updatedAt: datetime
DatasetWithReferences: # GET datasets
extends: DatasetCore
properties:
items:
type: list<string>
docs: list of dataset item ids
runs:
type: list<string>
docs: list of dataset run names
Dataset: # GET datasets/[name]
docs: Dataset including all items
extends: DatasetCore
properties:
items: list<DatasetItem>
runs: list<string>
DatasetItem:
properties:
id: string
status: DatasetStatus
input: unknown
input: optional<unknown>
expectedOutput: optional<unknown>
sourceTraceId: optional<string>
sourceObservationId: optional<string>
datasetId: string
createdAt: datetime
@@ -146,15 +161,18 @@ types:
id: string
datasetRunId: string
datasetItemId: string
observationId: string
traceId: string
observationId: optional<string>
createdAt: datetime
updatedAt: datetime
DatasetRun:
properties:
id: string
name: string
description: optional<string>
metadata: optional<unknown>
datasetId: string
datasetName: string
createdAt: datetime
updatedAt: datetime
datasetRunItems: list<DatasetRunItem>
@@ -24,7 +24,7 @@ types:
CreateDatasetItemRequest:
properties:
datasetName: string
input: unknown
input: optional<unknown>
expectedOutput: optional<unknown>
id:
type: optional<string>
@@ -15,8 +15,14 @@ types:
CreateDatasetRunItemRequest:
properties:
runName: string
runDescription:
type: optional<string>
docs: Description of the run. If run exists, description will be updated.
metadata:
type: optional<unknown>
docs: Metadata of the dataset run, updates run if run already exists
datasetItemId: string
observationId: string
observationId: optional<string>
traceId:
type: optional<string>
docs: traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId.
+16
View File
@@ -1,10 +1,21 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
imports:
commons: ./commons.yml
pagination: ./utils/pagination.yml
service:
auth: true
base-path: /api/public
endpoints:
list:
method: GET
docs: Get all datasets
path: /datasets
request:
name: GetDatasetsRequest
query-parameters:
page: optional<integer>
limit: optional<integer>
response: PaginatedDatasets
get:
method: GET
docs: Get a dataset and its items
@@ -28,6 +39,11 @@ service:
response: commons.DatasetRun
types:
PaginatedDatasets:
properties:
data: list<commons.DatasetWithReferences>
meta: pagination.MetaResponse
CreateDatasetRequest:
properties:
name: string
description: optional<string>
+6 -2
View File
@@ -13,8 +13,12 @@ service:
request:
name: GetDailyMetricsRequest
query-parameters:
page: optional<integer>
limit: optional<integer>
page:
type: optional<integer>
docs: page number, starts at 1
limit:
type: optional<integer>
docs: limit of items per page
traceName:
type: optional<string>
docs: Optional filter by the name of the trace
+9 -2
View File
@@ -22,13 +22,20 @@ service:
request:
name: GetObservationsRequest
query-parameters:
page: optional<integer>
limit: optional<integer>
page:
type: optional<integer>
docs: page number, starts at 1
limit:
type: optional<integer>
docs: limit of items per page
name: optional<string>
userId: optional<string>
type: optional<string>
traceId: optional<string>
parentObservationId: optional<string>
fromStartTime:
type: optional<datetime>
docs: Retrieve only observations with a start_time greater than this timestamp.
response: ObservationsViews
types:
+9 -2
View File
@@ -19,10 +19,17 @@ service:
request:
name: GetScoresRequest
query-parameters:
page: optional<integer>
limit: optional<integer>
page:
type: optional<integer>
docs: page number, starts at 1
limit:
type: optional<integer>
docs: limit of items per page
userId: optional<string>
name: optional<string>
fromTimestamp:
type: optional<datetime>
docs: Retrieve only scores newer than this timestamp.
response: Scores
get-by-id:
docs: Get a score
+9 -2
View File
@@ -22,10 +22,17 @@ service:
request:
name: GetTracesRequest
query-parameters:
page: optional<integer>
limit: optional<integer>
page:
type: optional<integer>
docs: page number, starts at 1
limit:
type: optional<integer>
docs: limit of items per page
userId: optional<string>
name: optional<string>
fromTimestamp:
type: optional<datetime>
docs: Retrieve only traces newer than this timestamp.
orderBy:
type: optional<string>
docs: "Format of the string [field].[asc/desc]. Fields: id, timestamp, name, userId, release, version, public, bookmarked, sessionId. Example: timestamp.asc"
+3 -1
View File
@@ -11,14 +11,16 @@ groups:
namespaceExport: Langfuse
allowCustomFetcher: true
- name: fernapi/fern-python-sdk
version: 0.7.3
version: 1.1.0-rc2
output:
location: local-file-system
path: ../../../generated/python
config:
client_class_name: FernLangfuse
improved_imports: false
pydantic_config:
require_optional_fields: false
use_str_enums: false
- name: fernapi/fern-typescript-node-sdk
version: 0.7.1
output:
+1 -1
View File
@@ -1,4 +1,4 @@
{
"organization": "finto",
"version": "0.16.36"
"version": "0.19.30"
}
-104
View File
@@ -1,104 +0,0 @@
openapi: 3.0.1
info:
title: langfuse
version: ''
paths:
/api/public/scores:
post:
description: Add a score to the database, upserts on id
operationId: score_create
tags:
- Score
parameters: []
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Score'
'400':
description: ''
content:
application/json:
schema:
type: string
'401':
description: ''
content:
application/json:
schema:
type: string
'403':
description: ''
content:
application/json:
schema:
type: string
'405':
description: ''
content:
application/json:
schema:
type: string
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateScoreRequest'
components:
schemas:
CreateScoreRequest:
title: CreateScoreRequest
type: object
properties:
id:
type: string
traceId:
type: string
name:
type: string
value:
type: number
format: double
observationId:
type: string
comment:
type: string
required:
- traceId
- name
- value
Score:
title: Score
type: object
properties:
id:
type: string
traceId:
type: string
name:
type: string
value:
type: number
format: double
observationId:
type: string
timestamp:
type: string
format: date-time
comment:
type: string
required:
- id
- traceId
- name
- value
- timestamp
securitySchemes:
BearerAuth:
type: http
scheme: bearer
File diff suppressed because it is too large Load Diff
+64 -13
View File
@@ -137,7 +137,7 @@
"auth": null,
"body": {
"mode": "raw",
"raw": "{\n \"runName\": \"example\",\n \"metadata\": \"UNKNOWN\",\n \"datasetItemId\": \"example\",\n \"observationId\": \"example\"\n}",
"raw": "{\n \"runName\": \"example\",\n \"runDescription\": \"example\",\n \"metadata\": \"UNKNOWN\",\n \"datasetItemId\": \"example\",\n \"observationId\": \"example\",\n \"traceId\": \"example\"\n}",
"options": {
"raw": {
"language": "json"
@@ -154,6 +154,42 @@
"description": null,
"name": "Datasets",
"item": [
{
"_type": "endpoint",
"name": "List",
"request": {
"description": "Get all datasets",
"url": {
"raw": "{{baseUrl}}/api/public/datasets?page=&limit=",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"public",
"datasets"
],
"query": [
{
"key": "page",
"value": "",
"description": null
},
{
"key": "limit",
"value": "",
"description": null
}
],
"variable": []
},
"header": [],
"method": "GET",
"auth": null,
"body": null
},
"response": []
},
{
"_type": "endpoint",
"name": "Get",
@@ -209,7 +245,7 @@
"auth": null,
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"example\"\n}",
"raw": "{\n \"name\": \"example\",\n \"description\": \"example\"\n}",
"options": {
"raw": {
"language": "json"
@@ -357,12 +393,12 @@
{
"key": "page",
"value": "",
"description": null
"description": "page number, starts at 1"
},
{
"key": "limit",
"value": "",
"description": null
"description": "limit of items per page"
},
{
"key": "traceName",
@@ -434,7 +470,7 @@
"request": {
"description": "Get a list of observations",
"url": {
"raw": "{{baseUrl}}/api/public/observations?page=&limit=&name=&userId=&type=&traceId=&parentObservationId=",
"raw": "{{baseUrl}}/api/public/observations?page=&limit=&name=&userId=&type=&traceId=&parentObservationId=&fromStartTime=",
"host": [
"{{baseUrl}}"
],
@@ -447,12 +483,12 @@
{
"key": "page",
"value": "",
"description": null
"description": "page number, starts at 1"
},
{
"key": "limit",
"value": "",
"description": null
"description": "limit of items per page"
},
{
"key": "name",
@@ -478,6 +514,11 @@
"key": "parentObservationId",
"value": "",
"description": null
},
{
"key": "fromStartTime",
"value": "",
"description": "Retrieve only observations with a start_time greater than this timestamp."
}
],
"variable": []
@@ -643,7 +684,7 @@
"request": {
"description": "Get a list of scores",
"url": {
"raw": "{{baseUrl}}/api/public/scores?page=&limit=&userId=&name=",
"raw": "{{baseUrl}}/api/public/scores?page=&limit=&userId=&name=&fromTimestamp=",
"host": [
"{{baseUrl}}"
],
@@ -656,12 +697,12 @@
{
"key": "page",
"value": "",
"description": null
"description": "page number, starts at 1"
},
{
"key": "limit",
"value": "",
"description": null
"description": "limit of items per page"
},
{
"key": "userId",
@@ -672,6 +713,11 @@
"key": "name",
"value": "",
"description": null
},
{
"key": "fromTimestamp",
"value": "",
"description": "Retrieve only scores newer than this timestamp."
}
],
"variable": []
@@ -831,7 +877,7 @@
"request": {
"description": "Get list of traces",
"url": {
"raw": "{{baseUrl}}/api/public/traces?page=&limit=&userId=&name=&orderBy=&tags=",
"raw": "{{baseUrl}}/api/public/traces?page=&limit=&userId=&name=&fromTimestamp=&orderBy=&tags=",
"host": [
"{{baseUrl}}"
],
@@ -844,12 +890,12 @@
{
"key": "page",
"value": "",
"description": null
"description": "page number, starts at 1"
},
{
"key": "limit",
"value": "",
"description": null
"description": "limit of items per page"
},
{
"key": "userId",
@@ -861,6 +907,11 @@
"value": "",
"description": null
},
{
"key": "fromTimestamp",
"value": "",
"description": "Retrieve only traces newer than this timestamp."
},
{
"key": "orderBy",
"value": "",
+5 -5
View File
@@ -8,14 +8,14 @@
},
"scripts": {
"preinstall": "npx only-allow pnpm",
"infra:dev:up": "docker-compose -f ./docker-compose.dev.yml up -d",
"infra:dev:down": "docker-compose -f ./docker-compose.dev.yml down",
"infra:dev:up": "docker compose -f ./docker-compose.dev.yml up -d",
"infra:dev:down": "docker compose -f ./docker-compose.dev.yml down",
"db:generate": "turbo run db:generate",
"db:migrate": "turbo run db:migrate",
"db:seed": "turbo run db:seed",
"db:seed:examples": "turbo run db:seed:examples",
"nuke": "bash ./scripts/nuke.sh",
"dx": "pnpm i && pnpm --filter=shared run db:reset && pnpm --filter=worker run db:seed:examples && pnpm run dev",
"dx": "pnpm i && pnpm --filter=shared run db:reset && pnpm --filter=shared run db:seed:examples && pnpm run dev",
"build": "turbo run build",
"start": "turbo run start",
"dev": "turbo run dev",
@@ -24,8 +24,8 @@
"models:migrate": "turbo run models:migrate"
},
"devDependencies": {
"dotenv-cli": "^7.3.0",
"dotenv-cli": "^7.4.1",
"prettier": "^3.2.5",
"turbo": "^1.13.0"
"turbo": "^1.13.2"
}
}
+2 -2
View File
@@ -8,12 +8,12 @@
"next.js"
],
"devDependencies": {
"@vercel/style-guide": "^5.2.0",
"@vercel/style-guide": "^6.0.0",
"eslint-config-turbo": "^1.12.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-only-warn": "^1.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"typescript": "^5.3.3"
"typescript": "^5.4.4"
}
}
+8 -8
View File
@@ -28,14 +28,14 @@
"seed": "ts-node -r tsconfig-paths/register -r dotenv/config --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
},
"dependencies": {
"@langchain/anthropic": "^0.1.8",
"@langchain/core": "^0.1.49",
"@langchain/openai": "^0.0.23",
"@prisma/client": "^5.11.0",
"@langchain/anthropic": "^0.1.10",
"@langchain/core": "^0.1.54",
"@langchain/openai": "^0.0.26",
"@prisma/client": "^5.12.1",
"@types/bcryptjs": "^2.4.6",
"bcryptjs": "^2.4.3",
"kysely": "^0.27.3",
"langchain": "^0.1.28",
"langchain": "^0.1.31",
"prisma-extension-kysely": "^2.1.0",
"prisma-kysely": "^1.8.0",
"zod": "^3.22.4"
@@ -45,7 +45,7 @@
"@repo/typescript-config": "*",
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.29",
"@types/pg": "^8.11.2",
"@types/pg": "^8.11.4",
"@types/uuid": "^9.0.8",
"@typescript-eslint/parser": "^7.2.0",
"eslint": "^8.57.0",
@@ -55,12 +55,12 @@
"kysely-codegen": "^0.11.0",
"nodemon": "^3.0.3",
"prettier": "^3.2.5",
"prisma": "^5.11.0",
"prisma": "^5.12.1",
"prisma-erd-generator": "^1.11.2",
"ts-node": "^10.9.2",
"tsc-watch": "^6.0.4",
"tsup": "^8.0.2",
"typescript": "^5.3.3",
"typescript": "^5.4.4",
"vitest": "^1.3.1"
}
}
+6 -2
View File
@@ -95,6 +95,7 @@ export type CronJobs = {
export type Dataset = {
id: string;
name: string;
description: string | null;
project_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
@@ -102,8 +103,9 @@ export type Dataset = {
export type DatasetItem = {
id: string;
status: Generated<DatasetStatus>;
input: unknown;
input: unknown | null;
expected_output: unknown | null;
source_trace_id: string | null;
source_observation_id: string | null;
dataset_id: string;
created_at: Generated<Timestamp>;
@@ -113,13 +115,15 @@ export type DatasetRunItems = {
id: string;
dataset_run_id: string;
dataset_item_id: string;
observation_id: string;
trace_id: string;
observation_id: string | null;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
};
export type DatasetRuns = {
id: string;
name: string;
description: string | null;
metadata: unknown | null;
dataset_id: string;
created_at: Generated<Timestamp>;
@@ -0,0 +1,6 @@
-- AlterTable
ALTER TABLE "dataset_run_items" ADD COLUMN "trace_id" TEXT,
ALTER COLUMN "observation_id" DROP NOT NULL;
-- AddForeignKey
ALTER TABLE "dataset_run_items" ADD CONSTRAINT "dataset_run_items_trace_id_fkey" FOREIGN KEY ("trace_id") REFERENCES "traces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- Backfill trace_id for existing run_items based on the linked observation
UPDATE dataset_run_items
SET trace_id = observations.trace_id
FROM observations
WHERE dataset_run_items.observation_id = observations.id;
@@ -0,0 +1,8 @@
/*
Warnings:
- Made the column `trace_id` on table `dataset_run_items` required. This step will fail if there are existing NULL values in that column.
*/
-- AlterTable
ALTER TABLE "dataset_run_items" ALTER COLUMN "trace_id" SET NOT NULL;
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_run_items_trace_id_idx" ON "dataset_run_items"("trace_id");
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "dataset_items" ALTER COLUMN "input" DROP NOT NULL;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "dataset_items" ADD COLUMN "source_trace_id" TEXT;
-- AddForeignKey
ALTER TABLE "dataset_items" ADD CONSTRAINT "dataset_items_source_trace_id_fkey" FOREIGN KEY ("source_trace_id") REFERENCES "traces"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "dataset_runs" ADD COLUMN "description" TEXT;
-- AlterTable
ALTER TABLE "datasets" ADD COLUMN "description" TEXT;
@@ -0,0 +1,7 @@
-- Backfill source_trace_id for existing dataset_items based on the linked source_observation_id
UPDATE dataset_items
SET source_trace_id = observations.trace_id
FROM observations
WHERE dataset_items.source_observation_id = observations.id
AND dataset_items.source_observation_id IS NOT NULL
AND dataset_items.source_trace_id IS NULL
+20 -11
View File
@@ -206,7 +206,9 @@ model Trace {
sessionId String? @map("session_id")
session TraceSession? @relation(fields: [sessionId, projectId], references: [id, projectId])
scores Score[]
scores Score[]
DatasetRunItems DatasetRunItems[]
DatasetItem DatasetItem[]
@@index([projectId])
@@index([sessionId])
@@ -420,6 +422,7 @@ model CronJobs {
model Dataset {
id String @id @default(cuid())
name String
description String?
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
@@ -435,8 +438,10 @@ model Dataset {
model DatasetItem {
id String @id @default(cuid())
status DatasetStatus @default(ACTIVE)
input Json
input Json?
expectedOutput Json? @map("expected_output")
sourceTraceId String? @map("source_trace_id")
sourceTrace Trace? @relation(fields: [sourceTraceId], references: [id], onDelete: SetNull)
sourceObservationId String? @map("source_observation_id")
sourceObservation Observation? @relation(fields: [sourceObservationId], references: [id], onDelete: SetNull)
datasetId String @map("dataset_id")
@@ -458,6 +463,7 @@ enum DatasetStatus {
model DatasetRuns {
id String @id @default(cuid())
name String
description String?
metadata Json?
datasetId String @map("dataset_id")
dataset Dataset @relation(fields: [datasetId], references: [id], onDelete: Cascade)
@@ -471,19 +477,22 @@ model DatasetRuns {
}
model DatasetRunItems {
id String @id @default(cuid())
datasetRunId String @map("dataset_run_id")
datasetRun DatasetRuns @relation(fields: [datasetRunId], references: [id], onDelete: Cascade)
datasetItemId String @map("dataset_item_id")
datasetItem DatasetItem @relation(fields: [datasetItemId], references: [id], onDelete: Cascade)
observationId String @map("observation_id")
observation Observation @relation(fields: [observationId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
id String @id @default(cuid())
datasetRunId String @map("dataset_run_id")
datasetRun DatasetRuns @relation(fields: [datasetRunId], references: [id], onDelete: Cascade)
datasetItemId String @map("dataset_item_id")
datasetItem DatasetItem @relation(fields: [datasetItemId], references: [id], onDelete: Cascade)
traceId String @map("trace_id")
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
observationId String? @map("observation_id")
observation Observation? @relation(fields: [observationId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
@@index([datasetRunId], type: Hash)
@@index([datasetItemId], type: Hash)
@@index([observationId], type: Hash)
@@index([traceId])
@@map("dataset_run_items")
}
+38 -27
View File
@@ -149,15 +149,14 @@ async function main() {
const traceVolume = environment === "load" ? LOAD_TRACE_VOLUME : 100;
const { generationIds, traces, observations, scores, sessions, events } =
createObjects(
traceVolume,
envTags,
colorTags,
project1,
project2,
promptIds
);
const { traces, observations, scores, sessions, events } = createObjects(
traceVolume,
envTags,
colorTags,
project1,
project2,
promptIds
);
console.log(
`Seeding ${traces.length} traces, ${observations.length} observations, and ${scores.length} scores`
@@ -169,28 +168,37 @@ async function main() {
const dataset = await prisma.dataset.create({
data: {
name: `demo-dataset-${datasetNumber}`,
description:
datasetNumber === 0 ? "Dataset test description" : undefined,
projectId: project2.id,
},
});
const datasetItemIds = [];
for (let i = 0; i < 18; i++) {
const sourceObservationId =
Math.random() > 0.5
? generationIds[Math.floor(Math.random() * generationIds.length)]
const sourceObservation =
Math.random() > 0.3
? observations[Math.floor(Math.random() * observations.length)]
: undefined;
const datasetItem = await prisma.datasetItem.create({
data: {
datasetId: dataset.id,
sourceObservationId: sourceObservationId,
input: [
{
role: "user",
content: "How can i create a React component?",
},
],
sourceTraceId: sourceObservation?.traceId,
sourceObservationId:
Math.random() > 0.5 ? sourceObservation?.id : undefined,
input:
Math.random() > 0.3
? [
{
role: "user",
content: "How can i create a React component?",
},
]
: undefined,
expectedOutput:
"Creating a React component can be done in two ways: as a functional component or as a class component. Let's start with a basic example of both.",
Math.random() > 0.3
? "Creating a React component can be done in two ways: as a functional component or as a class component. Let's start with a basic example of both."
: undefined,
},
});
datasetItemIds.push(datasetItem.id);
@@ -200,6 +208,7 @@ async function main() {
const datasetRun = await prisma.datasetRuns.create({
data: {
name: `demo-dataset-run-${datasetRunNumber}`,
description: Math.random() > 0.5 ? "Dataset run description" : "",
datasetId: dataset.id,
metadata: [
undefined,
@@ -212,13 +221,19 @@ async function main() {
});
for (const datasetItemId of datasetItemIds) {
const runObservationId =
generationIds[Math.floor(Math.random() * generationIds.length)];
const relevantObservations = observations.filter(
(o) => o.projectId === project2.id
);
const observation =
relevantObservations[
Math.floor(Math.random() * relevantObservations.length)
];
await prisma.datasetRunItems.create({
data: {
datasetItemId,
observationId: runObservationId,
traceId: observation.traceId as string,
observationId: Math.random() > 0.5 ? observation.id : undefined,
datasetRunId: datasetRun.id,
},
});
@@ -354,7 +369,6 @@ function createObjects(
const observations: Prisma.ObservationCreateManyInput[] = [];
const scores: Prisma.ScoreCreateManyInput[] = [];
const sessions: Prisma.TraceSessionCreateManyInput[] = [];
const generationIds: string[] = [];
const events: Prisma.ObservationCreateManyInput[] = [];
for (let i = 0; i < traceVolume; i++) {
@@ -618,8 +632,6 @@ function createObjects(
source: ScoreSource.API,
});
generationIds.push(generation.id);
for (let l = 0; l < Math.floor(Math.random() * 2); l++) {
// random start time within span
const eventTs = new Date(
@@ -651,7 +663,6 @@ function createObjects(
).map((session) => JSON.parse(session) as Prisma.TraceSessionCreateManyInput);
return {
generationIds,
traces,
observations,
scores,
+1126 -1188
View File
File diff suppressed because it is too large Load Diff
+175 -22
View File
@@ -152,25 +152,32 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/CreateDatasetRunItemRequest'
/api/public/datasets/{datasetName}:
/api/public/datasets:
get:
description: Get a dataset and its items
operationId: datasets_get
description: Get all datasets
operationId: datasets_list
tags:
- Datasets
parameters:
- name: datasetName
in: path
required: true
- name: page
in: query
required: false
schema:
type: string
type: integer
nullable: true
- name: limit
in: query
required: false
schema:
type: integer
nullable: true
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Dataset'
$ref: '#/components/schemas/PaginatedDatasets'
'400':
description: ''
content:
@@ -197,7 +204,6 @@ paths:
application/json:
schema: {}
security: *ref_0
/api/public/datasets:
post:
description: Create a dataset
operationId: datasets_create
@@ -243,6 +249,51 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/CreateDatasetRequest'
/api/public/datasets/{datasetName}:
get:
description: Get a dataset and its items
operationId: datasets_get
tags:
- Datasets
parameters:
- name: datasetName
in: path
required: true
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Dataset'
'400':
description: ''
content:
application/json:
schema: {}
'401':
description: ''
content:
application/json:
schema: {}
'403':
description: ''
content:
application/json:
schema: {}
'404':
description: ''
content:
application/json:
schema: {}
'405':
description: ''
content:
application/json:
schema: {}
security: *ref_0
/api/public/datasets/{datasetName}/runs/{runName}:
get:
description: Get a dataset run and its items
@@ -396,12 +447,14 @@ paths:
parameters:
- name: page
in: query
description: page number, starts at 1
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
description: limit of items per page
required: false
schema:
type: integer
@@ -519,12 +572,14 @@ paths:
parameters:
- name: page
in: query
description: page number, starts at 1
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
description: limit of items per page
required: false
schema:
type: integer
@@ -559,6 +614,16 @@ paths:
schema:
type: string
nullable: true
- name: fromStartTime
in: query
description: >-
Retrieve only observations with a start_time greater than this
timestamp.
required: false
schema:
type: string
format: date-time
nullable: true
responses:
'200':
description: ''
@@ -782,12 +847,14 @@ paths:
parameters:
- name: page
in: query
description: page number, starts at 1
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
description: limit of items per page
required: false
schema:
type: integer
@@ -804,6 +871,14 @@ paths:
schema:
type: string
nullable: true
- name: fromTimestamp
in: query
description: Retrieve only scores newer than this timestamp.
required: false
schema:
type: string
format: date-time
nullable: true
responses:
'200':
description: ''
@@ -1025,12 +1100,14 @@ paths:
parameters:
- name: page
in: query
description: page number, starts at 1
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
description: limit of items per page
required: false
schema:
type: integer
@@ -1047,6 +1124,14 @@ paths:
schema:
type: string
nullable: true
- name: fromTimestamp
in: query
description: Retrieve only traces newer than this timestamp.
required: false
schema:
type: string
format: date-time
nullable: true
- name: orderBy
in: query
description: >-
@@ -1394,14 +1479,17 @@ components:
- value
- source
- timestamp
Dataset:
title: Dataset
DatasetCore:
title: DatasetCore
type: object
properties:
id:
type: string
name:
type: string
description:
type: string
nullable: true
projectId:
type: string
createdAt:
@@ -1410,6 +1498,36 @@ components:
updatedAt:
type: string
format: date-time
required:
- id
- name
- projectId
- createdAt
- updatedAt
DatasetWithReferences:
title: DatasetWithReferences
type: object
properties:
items:
type: array
items:
type: string
description: list of dataset item ids
runs:
type: array
items:
type: string
description: list of dataset run names
required:
- items
- runs
allOf:
- $ref: '#/components/schemas/DatasetCore'
Dataset:
title: Dataset
type: object
description: Dataset including all items
properties:
items:
type: array
items:
@@ -1419,13 +1537,10 @@ components:
items:
type: string
required:
- id
- name
- projectId
- createdAt
- updatedAt
- items
- runs
allOf:
- $ref: '#/components/schemas/DatasetCore'
DatasetItem:
title: DatasetItem
type: object
@@ -1434,9 +1549,13 @@ components:
type: string
status:
$ref: '#/components/schemas/DatasetStatus'
input: {}
input:
nullable: true
expectedOutput:
nullable: true
sourceTraceId:
type: string
nullable: true
sourceObservationId:
type: string
nullable: true
@@ -1451,7 +1570,6 @@ components:
required:
- id
- status
- input
- datasetId
- createdAt
- updatedAt
@@ -1465,8 +1583,11 @@ components:
type: string
datasetItemId:
type: string
traceId:
type: string
observationId:
type: string
nullable: true
createdAt:
type: string
format: date-time
@@ -1477,7 +1598,7 @@ components:
- id
- datasetRunId
- datasetItemId
- observationId
- traceId
- createdAt
- updatedAt
DatasetRun:
@@ -1488,10 +1609,15 @@ components:
type: string
name:
type: string
description:
type: string
nullable: true
metadata:
nullable: true
datasetId:
type: string
datasetName:
type: string
createdAt:
type: string
format: date-time
@@ -1506,6 +1632,7 @@ components:
- id
- name
- datasetId
- datasetName
- createdAt
- updatedAt
- datasetRunItems
@@ -1558,7 +1685,8 @@ components:
properties:
datasetName:
type: string
input: {}
input:
nullable: true
expectedOutput:
nullable: true
id:
@@ -1567,13 +1695,16 @@ components:
description: Dataset items are upserted on their id
required:
- datasetName
- input
CreateDatasetRunItemRequest:
title: CreateDatasetRunItemRequest
type: object
properties:
runName:
type: string
runDescription:
type: string
nullable: true
description: Description of the run. If run exists, description will be updated.
metadata:
nullable: true
description: Metadata of the dataset run, updates run if run already exists
@@ -1581,16 +1712,38 @@ components:
type: string
observationId:
type: string
nullable: true
traceId:
type: string
nullable: true
description: >-
traceId should always be provided. For compatibility with older SDK
versions it can also be inferred from the provided observationId.
required:
- runName
- datasetItemId
- observationId
PaginatedDatasets:
title: PaginatedDatasets
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/DatasetWithReferences'
meta:
$ref: '#/components/schemas/utilsMetaResponse'
required:
- data
- meta
CreateDatasetRequest:
title: CreateDatasetRequest
type: object
properties:
name:
type: string
description:
type: string
nullable: true
required:
- name
HealthResponse:
+56 -16
View File
@@ -6,6 +6,25 @@ await import("./src/env.mjs");
import { withSentryConfig } from "@sentry/nextjs";
import { env } from "./src/env.mjs";
/**
* CSP headers
* img-src https to allow loading images from SSO providers
*/
const cspHeader = `
default-src 'self' https://ph.langfuse.com https://*.posthog.com wss://*.crisp.chat https://*.crisp.chat;
script-src 'self' 'unsafe-eval' https://*.crisp.chat https://challenges.cloudflare.com https://ph.langfuse.com https://static.cloudflareinsights.com https://*.stripe.com;
style-src 'self' 'unsafe-inline' https://*.crisp.chat;
img-src 'self' https: blob: data:;
font-src 'self' https://*.crisp.chat;
frame-src 'self' https://challenges.cloudflare.com https://*.stripe.com;
worker-src 'self' blob:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
${env.LANGFUSE_CSP_ENFORCE_HTTPS === "true" ? "upgrade-insecure-requests; block-all-mixed-content;" : ""}
`;
/** @type {import("next").NextConfig} */
const nextConfig = {
transpilePackages: ["@langfuse/shared"],
@@ -32,27 +51,48 @@ const nextConfig = {
key: "x-frame-options",
value: "SAMEORIGIN",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Permissions-Policy",
value: "autoplay=*, fullscreen=*, microphone=*",
},
],
},
{
source: "/:path((?!api).*)*",
headers: [
{
key: "Content-Security-Policy",
value: cspHeader.replace(/\n/g, ""),
},
],
},
// Required to check authentication status from langfuse.com
...(env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined
? [
{
source: "/api/auth/session",
headers: [
{
key: "Access-Control-Allow-Origin",
value: "https://langfuse.com",
},
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Methods", value: "GET,POST" },
{
key: "Access-Control-Allow-Headers",
value: "Content-Type, Authorization",
},
],
},
]
{
source: "/api/auth/session",
headers: [
{
key: "Access-Control-Allow-Origin",
value: "https://langfuse.com",
},
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Methods", value: "GET,POST" },
{
key: "Access-Control-Allow-Headers",
value: "Content-Type, Authorization",
},
],
},
]
: []),
];
},
+36 -36
View File
@@ -1,14 +1,14 @@
{
"name": "web",
"version": "2.14.4",
"version": "2.16.2",
"private": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"scripts": {
"prebuild": "cp generated/openapi-client/openapi.yml public/openapi-client.yml && cp generated/openapi-server/openapi.yml public/openapi-server.yml",
"build": "dotenv -e ../.env -- next build",
"copy-openapi-spec": "cp generated/openapi-client/openapi.yml public/openapi-client.yml && cp generated/openapi-server/openapi.yml public/openapi-server.yml",
"build": "pnpm run copy-openapi-spec && INLINE_RUNTIME_CHUNK=false dotenv -e ../.env -- next build",
"dev": "dotenv -e ../.env -- next dev",
"lint": "dotenv -e ../.env -- next lint",
"lint:fix": "dotenv -e ../.env -- next lint --fix",
@@ -24,16 +24,16 @@
"dependencies": {
"@anthropic-ai/tokenizer": "^0.0.4",
"@aws-sdk/client-s3": "^3.507.0",
"@aws-sdk/lib-storage": "^3.515.0",
"@aws-sdk/s3-request-presigner": "^3.507.0",
"@aws-sdk/lib-storage": "^3.540.0",
"@aws-sdk/s3-request-presigner": "^3.540.0",
"@headlessui/react": "^1.7.18",
"@heroicons/react": "^2.1.1",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.3.4",
"@langchain/anthropic": "^0.1.8",
"@langchain/core": "^0.1.49",
"@langchain/openai": "^0.0.23",
"@langchain/anthropic": "^0.1.10",
"@langchain/core": "^0.1.54",
"@langchain/openai": "^0.0.26",
"@langfuse/shared": "workspace:*",
"@marsidev/react-turnstile": "^0.5.3",
"@marsidev/react-turnstile": "^0.5.4",
"@next-auth/prisma-adapter": "^1.0.7",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
@@ -55,14 +55,14 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@react-email/components": "^0.0.14",
"@react-email/components": "^0.0.16",
"@react-email/render": "^0.0.12",
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@sentry/nextjs": "^7.102.1",
"@sentry/node": "^7.107.0",
"@sentry/profiling-node": "^7.102.1",
"@sentry/types": "^7.88.0",
"@sentry/nextjs": "^7.109.0",
"@sentry/node": "^7.109.0",
"@sentry/profiling-node": "^7.109.0",
"@sentry/types": "^7.109.0",
"@t3-oss/env-nextjs": "^0.8.0",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^4.36.1",
@@ -72,36 +72,36 @@
"@trpc/next": "^10.45.0",
"@trpc/react-query": "^10.45.0",
"@trpc/server": "^10.45.0",
"ai": "^3.0.11",
"ai": "^3.0.18",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.1",
"core-js": "^3.36.0",
"core-js": "^3.36.1",
"cors": "^2.8.5",
"date-fns": "^3.3.1",
"decimal.js": "^10.4.3",
"exponential-backoff": "^3.1.1",
"js-tiktoken": "^1.0.10",
"kysely": "^0.27.3",
"langchain": "^0.1.28",
"langchain": "^0.1.31",
"lodash": "^4.17.21",
"lucide-react": "^0.330.0",
"next": "^14.1.0",
"lucide-react": "^0.364.0",
"next": "^14.1.4",
"next-auth": "^4.24.7",
"next-query-params": "^5.0.0",
"nodemailer": "^6.9.9",
"nodemailer": "^6.9.13",
"posthog-js": "^1.105.9",
"posthog-node": "^3.6.3",
"prisma": "^5.11.0",
"prisma": "^5.12.1",
"react": "18.2.0",
"react-day-picker": "^8.10.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.50.1",
"react-hook-form": "^7.51.2",
"react-icons": "^5.0.1",
"react-responsive": "^9.0.2",
"react18-json-view": "^0.2.8-canary.1",
"sonner": "^1.4.0",
"react18-json-view": "^0.2.8-canary.6",
"sonner": "^1.4.41",
"superjson": "2.2.1",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
@@ -115,35 +115,35 @@
"@playwright/test": "^1.41.2",
"@release-it/bumper": "^6.0.1",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@testing-library/react": "^14.2.2",
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
"@types/eslint": "^8.56.2",
"@types/eslint": "^8.56.7",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.14.202",
"@types/node": "20.10.5",
"@types/nodemailer": "^6.4.14",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@types/react": "^18.2.74",
"@types/react-dom": "^18.2.24",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"autoprefixer": "^10.4.17",
"autoprefixer": "^10.4.19",
"dotenv-cli": "^7.3.0",
"eslint": "^8.56.0",
"eslint-config-next": "^14.1.0",
"eslint-config-next": "^14.1.4",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"node-mocks-http": "^1.14.1",
"postcss": "^8.4.35",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11",
"prettier-plugin-tailwindcss": "^0.5.13",
"release-it": "^17.1.1",
"tailwindcss": "^3.4.1",
"tailwindcss": "^3.4.3",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.7.1",
"typescript": "^5.3.3"
"tsx": "^4.7.2",
"typescript": "^5.4.4"
},
"ct3aMetadata": {
"initVersion": "7.13.0"
+1
View File
@@ -0,0 +1 @@
openapi*.yml
-104
View File
@@ -1,104 +0,0 @@
openapi: 3.0.1
info:
title: langfuse
version: ''
paths:
/api/public/scores:
post:
description: Add a score to the database, upserts on id
operationId: score_create
tags:
- Score
parameters: []
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Score'
'400':
description: ''
content:
application/json:
schema:
type: string
'401':
description: ''
content:
application/json:
schema:
type: string
'403':
description: ''
content:
application/json:
schema:
type: string
'405':
description: ''
content:
application/json:
schema:
type: string
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateScoreRequest'
components:
schemas:
CreateScoreRequest:
title: CreateScoreRequest
type: object
properties:
id:
type: string
traceId:
type: string
name:
type: string
value:
type: number
format: double
observationId:
type: string
comment:
type: string
required:
- traceId
- name
- value
Score:
title: Score
type: object
properties:
id:
type: string
traceId:
type: string
name:
type: string
value:
type: number
format: double
observationId:
type: string
timestamp:
type: string
format: date-time
comment:
type: string
required:
- id
- traceId
- name
- value
- timestamp
securitySchemes:
BearerAuth:
type: http
scheme: bearer
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,7 +9,7 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 0.3,
tracesSampleRate: 0.2,
// Capture Replay for 100% of all sessions,
// plus for 100% of sessions with an error
+1 -1
View File
@@ -7,7 +7,7 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 0.3,
tracesSampleRate: 0.2,
// ...
+2 -3
View File
@@ -12,7 +12,6 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
tracesSampler: (samplingContext: SamplingContext) => {
if (
samplingContext.request &&
@@ -20,9 +19,9 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
samplingContext.request.url &&
samplingContext.request.url.includes("api/trpc")
) {
return 1.0;
return 0.9;
}
return 0.2;
return 0.15;
},
profilesSampleRate: 0.2, // Profiling sample rate is relative to tracesSampleRate
+201 -14
View File
@@ -11,6 +11,7 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
it("should create and get a dataset", async () => {
await makeAPICall("POST", "/api/public/datasets", {
name: "dataset-name",
description: "dataset-description",
});
const dbDataset = await prisma.dataset.findMany({
@@ -29,6 +30,94 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
expect(getDataset.status).toBe(200);
expect(getDataset.body).toMatchObject({
name: "dataset-name",
description: "dataset-description",
});
});
it("GET datasets", async () => {
await makeAPICall("POST", "/api/public/datasets", {
name: "dataset-name-1",
});
await makeAPICall("POST", "/api/public/datasets", {
name: "dataset-name-2",
});
const datasetItemId = v4();
await makeAPICall("POST", "/api/public/dataset-items", {
datasetName: "dataset-name-2",
input: { key: "value" },
expectedOutput: { key: "value" },
id: datasetItemId,
});
const traceId = v4();
const observationId = v4();
const response = await makeAPICall("POST", "/api/public/ingestion", {
batch: [
{
id: v4(),
type: "trace-create",
timestamp: new Date().toISOString(),
body: {
id: traceId,
name: "trace-name",
userId: "user-1",
metadata: { key: "value" },
release: "1.0.0",
version: "2.0.0",
},
},
{
id: v4(),
type: "observation-create",
timestamp: new Date().toISOString(),
body: {
id: observationId,
traceId: traceId,
type: "GENERATION",
name: "generation-name",
startTime: "2021-01-01T00:00:00.000Z",
endTime: "2021-01-01T00:00:00.000Z",
modelParameters: { key: "value" },
input: { key: "value" },
metadata: { key: "value" },
version: "2.0.0",
},
},
],
});
expect(response.status).toBe(207);
await makeAPICall("POST", "/api/public/dataset-run-items", {
datasetItemId: datasetItemId,
observationId: observationId,
runName: "test-run",
metadata: { key: "value" },
});
const getDatasets = await makeAPICall("GET", `/api/public/datasets`);
expect(getDatasets.status).toBe(200);
expect(getDatasets.body).toMatchObject({
data: expect.arrayContaining([
expect.objectContaining({
name: "dataset-name-1",
items: [],
runs: [],
}),
expect.objectContaining({
name: "dataset-name-2",
items: [datasetItemId],
runs: ["test-run"],
}),
]),
meta: expect.objectContaining({
totalItems: 2,
page: 1,
}),
});
});
@@ -168,25 +257,123 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
});
expect(response.status).toBe(207);
const runItem = await makeAPICall("POST", "/api/public/dataset-run-items", {
datasetItemId: "dataset-item-id",
observationId: observationId,
runName: "run-name",
metadata: { key: "value" },
});
const dbRun = await prisma.datasetRuns.findFirst({
const runItemObservation = await makeAPICall(
"POST",
"/api/public/dataset-run-items",
{
datasetItemId: "dataset-item-id",
observationId: observationId,
runName: "run-only-observation",
runDescription: "run-description",
metadata: { key: "value" },
},
);
const dbRunObservation = await prisma.datasetRuns.findFirst({
where: {
name: "run-name",
name: "run-only-observation",
},
include: {
datasetRunItems: true,
},
});
expect(dbRun).not.toBeNull();
expect(dbRun?.datasetId).toBe(dataset.body.id);
expect(dbRun?.metadata).toMatchObject({ key: "value" });
expect(runItem.status).toBe(200);
expect(runItem.body).toMatchObject({
expect(dbRunObservation).not.toBeNull();
expect(dbRunObservation?.datasetId).toBe(dataset.body.id);
expect(dbRunObservation?.metadata).toMatchObject({ key: "value" });
expect(dbRunObservation?.description).toBe("run-description");
expect(runItemObservation.status).toBe(200);
expect(dbRunObservation?.datasetRunItems[0]).toMatchObject({
datasetItemId: "dataset-item-id",
observationId: observationId,
datasetRunId: dbRun?.id,
traceId: traceId,
});
const getRunAPI = await makeAPICall(
"GET",
`/api/public/datasets/dataset-name/runs/run-only-observation`,
);
expect(getRunAPI.status).toBe(200);
expect(getRunAPI.body).toMatchObject({
name: "run-only-observation",
description: "run-description",
metadata: { key: "value" },
datasetId: dataset.body.id,
datasetName: "dataset-name",
datasetRunItems: expect.arrayContaining([
expect.objectContaining({
datasetItemId: "dataset-item-id",
observationId: observationId,
traceId: traceId,
}),
]),
});
const runItemTrace = await makeAPICall(
"POST",
"/api/public/dataset-run-items",
{
datasetItemId: "dataset-item-id",
traceId: traceId,
runName: "run-only-trace",
metadata: { key: "value" },
},
);
const dbRunTrace = await prisma.datasetRuns.findFirst({
where: {
name: "run-only-trace",
},
include: {
datasetRunItems: true,
},
});
expect(dbRunTrace).not.toBeNull();
expect(dbRunTrace?.datasetId).toBe(dataset.body.id);
expect(dbRunTrace?.metadata).toMatchObject({ key: "value" });
expect(runItemTrace.status).toBe(200);
expect(dbRunTrace?.datasetRunItems[0]).toMatchObject({
datasetItemId: "dataset-item-id",
traceId: traceId,
observationId: null,
});
const runItemBoth = await makeAPICall(
"POST",
"/api/public/dataset-run-items",
{
datasetItemId: "dataset-item-id",
observationId: observationId,
traceId: traceId,
runName: "run-name-both",
metadata: { key: "value" },
},
);
const dbRunBoth = await prisma.datasetRuns.findFirst({
where: {
name: "run-name-both",
},
include: {
datasetRunItems: true,
},
});
expect(dbRunBoth).not.toBeNull();
expect(dbRunBoth?.datasetId).toBe(dataset.body.id);
expect(dbRunBoth?.metadata).toMatchObject({ key: "value" });
expect(runItemBoth.status).toBe(200);
expect(dbRunBoth?.datasetRunItems[0]).toMatchObject({
datasetItemId: "dataset-item-id",
observationId: observationId,
traceId: traceId,
});
});
it("dataset-run-items should fail when neither trace nor observation provided", async () => {
const response = await makeAPICall(
"POST",
"/api/public/dataset-run-items",
{
datasetItemId: "dataset-item-id",
runName: "run-fail",
},
);
expect(response.status).toBe(400);
});
});
+11 -10
View File
@@ -21,7 +21,7 @@ export type ScoresTableRow = {
value: number;
comment?: string;
observationId?: string;
traceName: string;
traceName?: string;
userId?: string;
};
@@ -102,15 +102,16 @@ export default function ScoresTable({
header: "Observation ID",
enableSorting: true,
cell: ({ row }) => {
const observationId = row.getValue("observationId");
const traceId = row.getValue("traceId");
return typeof observationId === "string" &&
typeof traceId === "string" ? (
const observationId = row.getValue(
"observationId",
) as ScoresTableRow["observationId"];
const traceId = row.getValue("traceId") as ScoresTableRow["traceId"];
return traceId && observationId ? (
<TableLink
path={`/project/${projectId}/traces/${traceId}?observation=${observationId}`}
value={observationId}
/>
) : null;
) : undefined;
},
},
{
@@ -120,17 +121,17 @@ export default function ScoresTable({
enableHiding: true,
enableSorting: true,
cell: ({ row }) => {
const value: string = row.getValue("traceName");
const value = row.getValue("traceName") as ScoresTableRow["traceName"];
const filter = encodeURIComponent(
`name;stringOptions;;any of;${value}`,
);
return (
return value ? (
<TableLink
path={`/project/${projectId}/traces?filter=${value ? filter : ""}`}
value={value}
truncateAt={40}
/>
);
) : undefined;
},
},
{
@@ -199,7 +200,7 @@ export default function ScoresTable({
comment: score.comment ?? undefined,
observationId: score.observationId ?? undefined,
traceId: score.traceId,
traceName: score.traceName,
traceName: score.traceName ?? undefined,
userId: score.userId ?? undefined,
};
};
+4 -21
View File
@@ -124,6 +124,7 @@ export default function TracesTable({
filter: filterState,
searchQuery,
orderBy: orderByState,
returnIO: false,
};
const traces = api.traces.all.useQuery(tracesAllQueryFilter);
@@ -650,22 +651,8 @@ const TracesIOCell = ({
traceId: string;
io: "input" | "output";
}) => {
const trace = api.traces.all.useQuery(
{
projectId: projectId,
filter: [
{
column: "id",
type: "string",
operator: "=",
value: traceId,
},
],
searchQuery: null,
orderBy: null,
page: 0,
limit: 1,
},
const trace = api.traces.byId.useQuery(
{ traceId: traceId },
{
enabled: typeof traceId === "string",
trpc: {
@@ -678,11 +665,7 @@ const TracesIOCell = ({
return (
<IOCell
isLoading={trace.isLoading}
data={
io === "output"
? trace.data?.traces[0]?.output
: trace.data?.traces[0]?.input
}
data={io === "output" ? trace.data?.output : trace.data?.input}
/>
);
};
@@ -17,7 +17,7 @@ import {
TableRow,
} from "@/src/components/ui/table";
import { ManualScoreButton } from "@/src/features/manual-scoring/components/ManualScoreButton";
import { NewDatasetItemFromObservationButton } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
import { NewDatasetItemFromTrace } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
import { type ObservationReturnType } from "@/src/server/api/routers/traces";
import { api } from "@/src/utils/api";
import { IOPreview } from "@/src/components/trace/IOPreview";
@@ -128,11 +128,12 @@ export const ObservationPreview = (props: {
scores={props.scores}
/>
{observationWithInputAndOutput.data ? (
<NewDatasetItemFromObservationButton
<NewDatasetItemFromTrace
traceId={preloadedObservation.traceId}
observationId={preloadedObservation.id}
projectId={props.projectId}
observationInput={observationWithInputAndOutput.data.input}
observationOutput={observationWithInputAndOutput.data.output}
input={observationWithInputAndOutput.data.input}
output={observationWithInputAndOutput.data.output}
key={preloadedObservation.id}
/>
) : null}
+15 -5
View File
@@ -22,6 +22,7 @@ import { Badge } from "@/src/components/ui/badge";
import { type ObservationReturnType } from "@/src/server/api/routers/traces";
import { IOPreview } from "@/src/components/trace/IOPreview";
import { formatIntervalSeconds } from "@/src/utils/dates";
import { NewDatasetItemFromTrace } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
export const TracePreview = ({
trace,
@@ -58,11 +59,20 @@ export const TracePreview = ({
)}
</div>
</div>
<ManualScoreButton
projectId={trace.projectId}
traceId={trace.id}
scores={scores}
/>
<div className="flex gap-2">
<ManualScoreButton
projectId={trace.projectId}
traceId={trace.id}
scores={scores}
/>
<NewDatasetItemFromTrace
traceId={trace.id}
projectId={trace.projectId}
input={trace.input}
output={trace.output}
key={trace.id}
/>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<IOPreview
+1 -1
View File
@@ -1 +1 @@
export const VERSION = "v2.14.4";
export const VERSION = "v2.16.2";
+18
View File
@@ -39,6 +39,7 @@ export const env = createEnv({
LANGFUSE_DEFAULT_PROJECT_ROLE: z
.enum(["ADMIN", "MEMBER", "VIEWER"])
.optional(),
LANGFUSE_CSP_ENFORCE_HTTPS: z.enum(["true", "false"]).optional(),
// AUTH
AUTH_GOOGLE_CLIENT_ID: z.string().optional(),
AUTH_GOOGLE_CLIENT_SECRET: z.string().optional(),
@@ -50,6 +51,14 @@ export const env = createEnv({
AUTH_AZURE_AD_CLIENT_SECRET: z.string().optional(),
AUTH_AZURE_AD_TENANT_ID: z.string().optional(),
AUTH_AZURE_ALLOW_ACCOUNT_LINKING: z.enum(["true", "false"]).optional(),
AUTH_OKTA_CLIENT_ID: z.string().optional(),
AUTH_OKTA_CLIENT_SECRET: z.string().optional(),
AUTH_OKTA_ISSUER: z.string().optional(),
AUTH_OKTA_ALLOW_ACCOUNT_LINKING: z.enum(["true", "false"]).optional(),
AUTH_AUTH0_CLIENT_ID: z.string().optional(),
AUTH_AUTH0_CLIENT_SECRET: z.string().optional(),
AUTH_AUTH0_ISSUER: z.string().url().optional(),
AUTH_AUTH0_ALLOW_ACCOUNT_LINKING: z.enum(["true", "false"]).optional(),
AUTH_DOMAINS_WITH_SSO_ENFORCEMENT: z.string().optional(),
AUTH_DISABLE_USERNAME_PASSWORD: z.enum(["true", "false"]).optional(),
// EMAIL
@@ -107,6 +116,7 @@ export const env = createEnv({
LANGFUSE_NEW_USER_SIGNUP_WEBHOOK:
process.env.LANGFUSE_NEW_USER_SIGNUP_WEBHOOK,
SALT: process.env.SALT,
LANGFUSE_CSP_ENFORCE_HTTPS: process.env.LANGFUSE_CSP_ENFORCE_HTTPS,
// Default project and role
LANGFUSE_DEFAULT_PROJECT_ID: process.env.LANGFUSE_DEFAULT_PROJECT_ID,
LANGFUSE_DEFAULT_PROJECT_ROLE: process.env.LANGFUSE_DEFAULT_PROJECT_ROLE,
@@ -124,6 +134,14 @@ export const env = createEnv({
AUTH_AZURE_AD_TENANT_ID: process.env.AUTH_AZURE_AD_TENANT_ID,
AUTH_AZURE_ALLOW_ACCOUNT_LINKING:
process.env.AUTH_AZURE_ALLOW_ACCOUNT_LINKING,
AUTH_OKTA_CLIENT_ID: process.env.AUTH_OKTA_CLIENT_ID,
AUTH_OKTA_CLIENT_SECRET: process.env.AUTH_OKTA_CLIENT_SECRET,
AUTH_OKTA_ISSUER: process.env.AUTH_OKTA_ISSUER,
AUTH_OKTA_ALLOW_ACCOUNT_LINKING: process.env.AUTH_OKTA_ALLOW_ACCOUNT_LINKING,
AUTH_AUTH0_CLIENT_ID: process.env.AUTH_AUTH0_CLIENT_ID,
AUTH_AUTH0_CLIENT_SECRET: process.env.AUTH_AUTH0_CLIENT_SECRET,
AUTH_AUTH0_ISSUER: process.env.AUTH_AUTH0_ISSUER,
AUTH_AUTH0_ALLOW_ACCOUNT_LINKING: process.env.AUTH_AUTH0_ALLOW_ACCOUNT_LINKING,
AUTH_DOMAINS_WITH_SSO_ENFORCEMENT:
process.env.AUTH_DOMAINS_WITH_SSO_ENFORCEMENT,
AUTH_DISABLE_USERNAME_PASSWORD: process.env.AUTH_DISABLE_USERNAME_PASSWORD,
@@ -30,7 +30,10 @@ export const TabComponent = ({ tabs }: TabComponentProps) => {
</div>
<div className="hidden sm:block">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
<nav
className="-mb-px flex space-x-2 md:space-x-4 lg:space-x-6 xl:space-x-8"
aria-label="Tabs"
>
{tabs.map((tab, index) => (
<a
key={tab.tabTitle}
@@ -13,7 +13,7 @@ import { DatasetForm } from "@/src/features/datasets/components/DatasetForm";
import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
interface BaseDatasetButtonProps {
mode: "create" | "rename" | "delete";
mode: "create" | "update" | "delete";
projectId: string;
className?: string;
onFormSuccess?: () => void;
@@ -28,16 +28,17 @@ interface DeleteDatasetButtonProps extends BaseDatasetButtonProps {
datasetId: string;
}
interface RenameDatasetButtonProps extends BaseDatasetButtonProps {
mode: "rename";
interface UpdateDatasetButtonProps extends BaseDatasetButtonProps {
mode: "update";
datasetId: string;
datasetName: string;
datasetDescription?: string;
icon?: boolean;
}
type DatasetActionButtonProps =
| CreateDatasetButtonProps
| RenameDatasetButtonProps
| UpdateDatasetButtonProps
| DeleteDatasetButtonProps;
export const DatasetActionButton = (props: DatasetActionButtonProps) => {
@@ -50,7 +51,7 @@ export const DatasetActionButton = (props: DatasetActionButtonProps) => {
return (
<Dialog open={hasAccess && open} onOpenChange={setOpen}>
<DialogTrigger asChild>
{props.mode === "rename" ? (
{props.mode === "update" ? (
props.icon ? (
<Button
variant="outline"
@@ -99,7 +100,7 @@ export const DatasetActionButton = (props: DatasetActionButtonProps) => {
? "Create new dataset"
: props.mode === "delete"
? "Please confirm"
: "Rename dataset"}
: "Update dataset"}
</DialogTitle>
{props.mode === "delete" && (
<DialogDescription className="text-md p-0">
@@ -123,11 +124,12 @@ export const DatasetActionButton = (props: DatasetActionButtonProps) => {
/>
) : (
<DatasetForm
mode="rename"
mode="update"
projectId={props.projectId}
onFormSuccess={() => setOpen(false)}
datasetId={props.datasetId}
datasetName={props.datasetName}
datasetDescription={props.datasetDescription}
/>
)}
</DialogContent>
@@ -16,7 +16,7 @@ import { usePostHog } from "posthog-js/react";
import { Input } from "@/src/components/ui/input";
interface BaseDatasetFormProps {
mode: "create" | "rename" | "delete";
mode: "create" | "update" | "delete";
projectId: string;
onFormSuccess?: () => void;
className?: string;
@@ -31,15 +31,16 @@ interface DeleteDatasetFormProps extends BaseDatasetFormProps {
datasetId: string;
}
interface RenameDatasetFormProps extends BaseDatasetFormProps {
mode: "rename";
interface UpdateDatasetFormProps extends BaseDatasetFormProps {
mode: "update";
datasetId: string;
datasetName: string;
datasetDescription?: string;
}
type DatasetFormProps =
| CreateDatasetFormProps
| RenameDatasetFormProps
| UpdateDatasetFormProps
| DeleteDatasetFormProps;
const formSchema = z.object({
@@ -49,6 +50,7 @@ const formSchema = z.object({
.refine((name) => name.trim().length > 0, {
message: "Input should not be only whitespace",
}),
description: z.string(),
});
export const DatasetForm = (props: DatasetFormProps) => {
@@ -56,9 +58,16 @@ export const DatasetForm = (props: DatasetFormProps) => {
const posthog = usePostHog();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
},
defaultValues:
props.mode === "update"
? {
name: props.datasetName,
description: props.datasetDescription ?? "",
}
: {
name: "",
description: "",
},
});
const utils = api.useUtils();
@@ -70,6 +79,7 @@ export const DatasetForm = (props: DatasetFormProps) => {
const trimmedValues = {
...values,
name: values.name.trim(),
description: values.description !== "" ? values.description.trim() : null,
};
if (props.mode === "create") {
posthog.capture("datasets:new_dataset_form_submit");
@@ -87,8 +97,8 @@ export const DatasetForm = (props: DatasetFormProps) => {
setFormError(error.message);
console.error(error);
});
} else if (props.mode === "rename") {
posthog.capture("datasets:rename_dataset_form_submit");
} else if (props.mode === "update") {
posthog.capture("datasets:update_dataset_form_submit");
renameMutation
.mutateAsync({
...trimmedValues,
@@ -134,27 +144,36 @@ export const DatasetForm = (props: DatasetFormProps) => {
onSubmit={
props.mode === "delete" ? handleDelete : form.handleSubmit(onSubmit)
}
className="space-y-8"
>
{props.mode !== "delete" && (
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder={
props.mode === "rename" ? props.datasetName : ""
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="mb-8 space-y-6">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description (optional)</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
<Button
type="submit"
@@ -166,7 +185,7 @@ export const DatasetForm = (props: DatasetFormProps) => {
? "Create dataset"
: props.mode === "delete"
? "Delete Dataset"
: "Rename dataset"}
: "Update dataset"}
</Button>
</form>
</Form>
@@ -1,6 +1,5 @@
import { DataTable } from "@/src/components/table/data-table";
import TableLink from "@/src/components/table/table-link";
import { NewDatasetItemButton } from "@/src/features/datasets/components/NewDatasetItemButton";
import { api } from "@/src/utils/api";
import { type RouterOutput } from "@/src/utils/types";
import {
@@ -154,7 +153,7 @@ export function DatasetItemsTable({
const convertToTableRow = (
item: RouterOutput["datasets"]["itemsByDatasetId"]["datasetItems"][number],
): RowData => {
let input = JSON.stringify(item.input);
let input = item.input ? JSON.stringify(item.input) : "";
input = input.length > 50 ? input.slice(0, 50) + "..." : input;
let expectedOutput = item.expectedOutput
? JSON.stringify(item.expectedOutput)
@@ -202,11 +201,6 @@ export function DatasetItemsTable({
state: paginationState,
}}
/>
<NewDatasetItemButton
projectId={projectId}
datasetId={datasetId}
className="mt-4"
/>
</div>
);
}
@@ -14,10 +14,13 @@ type RowData = {
id: string;
runAt: string;
datasetItemId: string;
observation: { id: string; traceId: string };
trace?: {
traceId: string;
observationId?: string;
};
scores: Score[];
latency: number;
totalCost: string;
latency?: number;
totalCost?: string;
};
export function DatasetRunItemsTable(
@@ -63,14 +66,21 @@ export function DatasetRunItemsTable(
},
},
{
accessorKey: "observation",
header: "Observation",
accessorKey: "trace",
header: "Trace",
cell: ({ row }) => {
const observation: RowData["observation"] = row.getValue("observation");
return (
const trace: RowData["trace"] = row.getValue("trace");
if (!trace) return null;
return trace.observationId ? (
<TableLink
path={`/project/${props.projectId}/traces/${observation.traceId}?observation=${observation.id}`}
value={observation.id}
path={`/project/${props.projectId}/traces/${trace.traceId}?observation=${trace.observationId}`}
value={trace.observationId}
truncateAt={7}
/>
) : (
<TableLink
path={`/project/${props.projectId}/traces/${trace.traceId}`}
value={trace.traceId}
truncateAt={7}
/>
);
@@ -81,7 +91,7 @@ export function DatasetRunItemsTable(
header: "Latency",
cell: ({ row }) => {
const latency: RowData["latency"] = row.getValue("latency");
return <>{formatIntervalSeconds(latency)}</>;
return <>{!!latency ? formatIntervalSeconds(latency) : null}</>;
},
},
{
@@ -109,18 +119,17 @@ export function DatasetRunItemsTable(
id: item.id,
runAt: item.createdAt.toISOString(),
datasetItemId: item.datasetItemId,
observation: {
id: item.observation.id,
traceId: item.observation.traceId ?? "", // never actually null, just not enforced by db
},
scores: item.observation.scores,
totalCost: usdFormatter(
item.observation.calculatedTotalCost?.toNumber() ?? 0,
),
latency: intervalInSeconds(
item.observation.startTime,
item.observation.endTime,
),
trace: !!item.trace?.id
? {
traceId: item.trace.id,
observationId: item.observation?.id,
}
: undefined,
scores: item.scores,
totalCost: !!item.observation?.calculatedTotalCost
? usdFormatter(item.observation.calculatedTotalCost.toNumber())
: undefined,
latency: item.observation?.latency ?? item.trace?.duration ?? undefined,
};
};
@@ -21,6 +21,7 @@ type RowData = {
avgLatency: number;
avgTotalCost: string;
scores: RouterOutput["datasets"]["runsByDatasetId"]["runs"][number]["scores"];
description: string;
metadata: string;
};
@@ -64,8 +65,8 @@ export function DatasetRunsTable(props: {
},
},
{
accessorKey: "createdAt",
header: "Created",
accessorKey: "description",
header: "Description",
},
{
accessorKey: "countRunItems",
@@ -104,6 +105,10 @@ export function DatasetRunsTable(props: {
);
},
},
{
accessorKey: "createdAt",
header: "Created",
},
{
accessorKey: "metadata",
header: "Metadata",
@@ -124,6 +129,7 @@ export function DatasetRunsTable(props: {
avgLatency: item.avgLatency,
avgTotalCost: usdFormatter(item.avgTotalCost.toNumber()),
scores: item.scores,
description: item.description ?? "",
metadata: JSON.stringify(item.metadata),
};
};
@@ -21,6 +21,7 @@ type RowData = {
id: string;
name: string;
};
description: string;
createdAt: string;
lastRunAt?: string;
countItems: number;
@@ -66,6 +67,10 @@ export function DatasetsTable(props: { projectId: string }) {
);
},
},
{
accessorKey: "description",
header: "Description",
},
{
accessorKey: "countItems",
header: "Items",
@@ -99,10 +104,11 @@ export function DatasetsTable(props: { projectId: string }) {
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DatasetActionButton
mode="rename"
mode="update"
projectId={props.projectId}
datasetId={key.id}
datasetName={key.name}
datasetDescription={row.getValue("description") ?? undefined}
/>
<DatasetActionButton
mode="delete"
@@ -121,6 +127,7 @@ export function DatasetsTable(props: { projectId: string }) {
): RowData => {
return {
key: { id: item.id, name: item.name },
description: item.description ?? "",
createdAt: item.createdAt.toLocaleString(),
lastRunAt: item.lastRunAt?.toLocaleString() ?? "",
countItems: item.countDatasetItems,
@@ -18,6 +18,7 @@ import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
const formSchema = z.object({
input: z.string().refine(
(value) => {
if (value === "") return true;
try {
JSON.parse(value);
return true;
@@ -133,7 +134,7 @@ export const EditDatasetItem = ({
name="expectedOutput"
render={({ field }) => (
<FormItem>
<FormLabel>Expected output (optional)</FormLabel>
<FormLabel>Expected output</FormLabel>
<FormControl>
<Textarea
{...field}
@@ -27,6 +27,7 @@ const formSchema = z.object({
datasetId: z.string().min(1, "Select a dataset"),
input: z.string().refine(
(value) => {
if (value === "") return true;
try {
JSON.parse(value);
return true;
@@ -58,9 +59,10 @@ const formSchema = z.object({
export const NewDatasetItemForm = (props: {
projectId: string;
traceId?: string;
observationId?: string;
observationInput?: Prisma.JsonValue;
observationOutput?: Prisma.JsonValue;
input?: Prisma.JsonValue;
output?: Prisma.JsonValue;
datasetId?: string;
onFormSuccess?: () => void;
}) => {
@@ -70,12 +72,8 @@ export const NewDatasetItemForm = (props: {
resolver: zodResolver(formSchema),
defaultValues: {
datasetId: props.datasetId ?? "",
input: props.observationInput
? JSON.stringify(props.observationInput, null, 2)
: "",
expectedOutput: props.observationOutput
? JSON.stringify(props.observationOutput, null, 2)
: "",
input: props.input ? JSON.stringify(props.input, null, 2) : "",
expectedOutput: props.output ? JSON.stringify(props.output, null, 2) : "",
},
});
@@ -91,12 +89,14 @@ export const NewDatasetItemForm = (props: {
function onSubmit(values: z.infer<typeof formSchema>) {
posthog.capture("datasets:new_dataset_item_form_submit", {
hasSourceTrace: !!props.traceId,
hasSourceObservation: !!props.observationId,
});
createDatasetItemMutation
.mutateAsync({
...values,
projectId: props.projectId,
sourceTraceId: props.traceId,
sourceObservationId: props.observationId,
})
.then(() => {
@@ -22,23 +22,26 @@ import { type Prisma } from "@langfuse/shared/src/db";
import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
import { useSession } from "next-auth/react";
export const NewDatasetItemFromObservationButton = (props: {
export const NewDatasetItemFromTrace = (props: {
projectId: string;
observationId: string;
observationInput: Prisma.JsonValue;
observationOutput: Prisma.JsonValue;
traceId: string;
observationId?: string;
input: Prisma.JsonValue;
output: Prisma.JsonValue;
}) => {
const [open, setOpen] = useState(false);
const session = useSession();
const observationInDatasets = api.datasets.observationInDatasets.useQuery(
{
projectId: props.projectId,
observationId: props.observationId,
},
{
enabled: session.status === "authenticated",
},
);
const observationInDatasets =
api.datasets.datasetItemsBasedOnTraceOrObservation.useQuery(
{
projectId: props.projectId,
traceId: props.traceId,
observationId: props.observationId,
},
{
enabled: session.status === "authenticated",
},
);
const hasAccess = useHasAccess({
projectId: props.projectId,
scope: "datasets:CUD",
@@ -103,10 +106,11 @@ export const NewDatasetItemFromObservationButton = (props: {
<DialogTitle className="mb-5">Add to dataset</DialogTitle>
</DialogHeader>
<NewDatasetItemForm
traceId={props.traceId}
observationId={props.observationId}
projectId={props.projectId}
observationInput={props.observationInput}
observationOutput={props.observationOutput}
input={props.input}
output={props.output}
onFormSuccess={() => setOpen(false)}
/>
</DialogContent>
@@ -42,6 +42,7 @@ export const datasetRouter = createTRPCRouter({
.select(({ eb }) => [
"datasets.id",
"datasets.name",
"datasets.description",
"datasets.created_at as createdAt",
"datasets.updated_at as updatedAt",
eb.fn.count("dataset_items.id").distinct().as("countDatasetItems"),
@@ -179,6 +180,7 @@ export const datasetRouter = createTRPCRouter({
SELECT
runs.id,
runs.name,
runs.description,
runs.metadata,
runs.created_at "createdAt",
runs.updated_at "updatedAt",
@@ -202,7 +204,8 @@ export const datasetRouter = createTRPCRouter({
5,
6,
7,
8
8,
9
ORDER BY
runs.created_at DESC
LIMIT ${input.limit}
@@ -292,6 +295,7 @@ export const datasetRouter = createTRPCRouter({
datasetItemId: z.string(),
input: z.string().optional(),
expectedOutput: z.string().optional(),
sourceTraceId: z.string().optional(),
sourceObservationId: z.string().optional(),
status: z.enum(["ACTIVE", "ARCHIVED"]).optional(),
}),
@@ -312,15 +316,18 @@ export const datasetRouter = createTRPCRouter({
},
data: {
input:
input.input !== undefined
? (JSON.parse(input.input) as Prisma.InputJsonObject)
: undefined,
input.input === ""
? Prisma.DbNull
: input.input !== undefined
? (JSON.parse(input.input) as Prisma.InputJsonObject)
: undefined,
expectedOutput:
input.expectedOutput === ""
? Prisma.DbNull
: input.expectedOutput !== undefined
? (JSON.parse(input.expectedOutput) as Prisma.InputJsonObject)
: undefined,
sourceTraceId: input.sourceTraceId,
sourceObservationId: input.sourceObservationId,
status: input.status,
},
@@ -336,7 +343,13 @@ export const datasetRouter = createTRPCRouter({
return datasetItem;
}),
createDataset: protectedProjectProcedure
.input(z.object({ projectId: z.string(), name: z.string() }))
.input(
z.object({
projectId: z.string(),
name: z.string(),
description: z.string().nullish(),
}),
)
.mutation(async ({ input, ctx }) => {
throwIfNoAccess({
session: ctx.session,
@@ -346,6 +359,7 @@ export const datasetRouter = createTRPCRouter({
const dataset = await ctx.prisma.dataset.create({
data: {
name: input.name,
description: input.description ?? undefined,
projectId: input.projectId,
},
});
@@ -366,7 +380,8 @@ export const datasetRouter = createTRPCRouter({
z.object({
projectId: z.string(),
datasetId: z.string(),
name: z.string(),
name: z.string().nullish(),
description: z.string().nullish(),
}),
)
.mutation(async ({ input, ctx }) => {
@@ -381,7 +396,8 @@ export const datasetRouter = createTRPCRouter({
projectId: input.projectId,
},
data: {
name: input.name,
name: input.name ?? undefined,
description: input.description,
},
});
await auditLog({
@@ -390,7 +406,6 @@ export const datasetRouter = createTRPCRouter({
resourceId: dataset.id,
projectId: input.projectId,
action: "update",
before: { name: input.name },
after: dataset,
});
@@ -425,8 +440,9 @@ export const datasetRouter = createTRPCRouter({
z.object({
projectId: z.string(),
datasetId: z.string(),
input: z.string(),
input: z.string().nullish(),
expectedOutput: z.string().nullish(),
sourceTraceId: z.string().optional(),
sourceObservationId: z.string().optional(),
}),
)
@@ -448,7 +464,12 @@ export const datasetRouter = createTRPCRouter({
const datasetItem = await ctx.prisma.datasetItem.create({
data: {
input: JSON.parse(input.input) as Prisma.InputJsonObject,
input:
input.input === ""
? Prisma.DbNull
: !!input.input
? (JSON.parse(input.input) as Prisma.InputJsonObject)
: undefined,
expectedOutput:
input.expectedOutput === ""
? Prisma.DbNull
@@ -456,6 +477,7 @@ export const datasetRouter = createTRPCRouter({
? (JSON.parse(input.expectedOutput) as Prisma.InputJsonObject)
: undefined,
datasetId: input.datasetId,
sourceTraceId: input.sourceTraceId,
sourceObservationId: input.sourceObservationId,
},
});
@@ -491,14 +513,21 @@ export const datasetRouter = createTRPCRouter({
datasetItemId: input.datasetItemId,
datasetRun: {
dataset: {
projectId: input.projectId,
projectId: ctx.session.projectId,
},
},
},
include: {
datasetItem: true,
observation: {
include: {
select: {
id: true,
scores: true,
},
},
trace: {
select: {
id: true,
scores: true,
},
},
@@ -516,18 +545,16 @@ export const datasetRouter = createTRPCRouter({
datasetItemId: input.datasetItemId,
datasetRun: {
dataset: {
projectId: input.projectId,
projectId: ctx.session.projectId,
},
},
},
});
const observationIds = runItems.map((ri) => ri.observationId);
const observationIds = runItems
.map((ri) => ri.observation?.id)
.filter(Boolean) as string[];
const observations = await ctx.prisma.observationView.findMany({
select: {
id: true,
calculatedTotalCost: true,
},
where: {
id: {
in: observationIds,
@@ -535,15 +562,29 @@ export const datasetRouter = createTRPCRouter({
},
});
const traceIds = runItems
.map((ri) => ri.trace?.id)
.filter(Boolean) as string[];
const traces = await ctx.prisma.traceView.findMany({
where: {
id: {
in: traceIds,
},
projectId: ctx.session.projectId,
},
});
const items = runItems.map((ri) => {
return {
id: ri.id,
createdAt: ri.createdAt,
datasetItemId: ri.datasetItemId,
observation: {
...ri.observation,
...observations.find((o) => o.id === ri.observationId),
},
observation: observations.find((o) => o.id === ri.observationId),
trace: traces.find((t) => t.id === ri.traceId),
scores:
// use observation scores if run is linked to an observation, otherwise use all trace scores
(!!ri.observationId ? ri.observation?.scores : ri.trace?.scores) ??
[],
};
});
@@ -552,17 +593,19 @@ export const datasetRouter = createTRPCRouter({
runItems: items,
};
}),
observationInDatasets: protectedProjectProcedure
datasetItemsBasedOnTraceOrObservation: protectedProjectProcedure
.input(
z.object({
projectId: z.string(),
observationId: z.string(),
traceId: z.string(),
observationId: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
return ctx.prisma.datasetItem.findMany({
where: {
sourceObservationId: input.observationId,
sourceTraceId: input.traceId,
sourceObservationId: input.observationId ?? null, // null as it should not include observations from the same trace
dataset: {
projectId: input.projectId,
},
+7 -6
View File
@@ -9,7 +9,7 @@ import { isPrismaException } from "@/src/utils/exceptions";
const CreateDatasetItemSchema = z.object({
datasetName: z.string(),
input: jsonSchema,
input: jsonSchema.nullish(),
expectedOutput: jsonSchema.nullish(),
id: z.string().nullish(),
});
@@ -41,10 +41,11 @@ export default async function handler(
const itemBody = CreateDatasetItemSchema.parse(req.body);
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all")
return res.status(403).json({
message: "Access denied",
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
// Check access to dataset
@@ -68,12 +69,12 @@ export default async function handler(
},
create: {
id,
input: itemBody.input,
input: itemBody.input ?? undefined,
expectedOutput: itemBody.expectedOutput ?? undefined,
datasetId: dataset.id,
},
update: {
input: itemBody.input,
input: itemBody.input ?? undefined,
expectedOutput: itemBody.expectedOutput ?? undefined,
},
});
@@ -38,10 +38,11 @@ export default async function handler(
const { datasetItemId } = GetDatasetItemQuerySchema.parse(req.query);
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all")
return res.status(403).json({
message: "Access denied",
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
// Check access to dataset
+62 -25
View File
@@ -6,12 +6,19 @@ import { verifyAuthHeaderAndReturnScope } from "@/src/features/public-api/server
import { isPrismaException } from "@/src/utils/exceptions";
import { jsonSchema } from "@/src/utils/zod";
const DatasetRunItemPostSchema = z.object({
runName: z.string(),
metadata: jsonSchema.nullish(),
datasetItemId: z.string(),
observationId: z.string(),
});
const DatasetRunItemPostSchema = z
.object({
runName: z.string(),
runDescription: z.string().nullish(),
metadata: jsonSchema.nullish(),
datasetItemId: z.string(),
observationId: z.string().nullish(),
traceId: z.string().nullish(),
})
.refine((data) => data.observationId || data.traceId, {
message: "ObservationId or traceId must be provided",
path: ["observationId", "traceId"], // Specify the path of the error
});
export default async function handler(
req: NextApiRequest,
@@ -33,8 +40,7 @@ export default async function handler(
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET scores",
message: "Access denied - need to use basic auth with secret key",
});
}
console.log(
@@ -43,10 +49,16 @@ export default async function handler(
", body:",
JSON.stringify(req.body, null, 2),
);
const { datasetItemId, observationId, runName, metadata } =
DatasetRunItemPostSchema.parse(req.body);
const {
datasetItemId,
observationId,
traceId,
runName,
runDescription,
metadata,
} = DatasetRunItemPostSchema.parse(req.body);
const item = await prisma.datasetItem.findUnique({
const datasetItem = await prisma.datasetItem.findUnique({
where: {
id: datasetItemId,
status: "ACTIVE",
@@ -58,48 +70,73 @@ export default async function handler(
dataset: true,
},
});
const observation = await prisma.observation.findUnique({
where: {
id: observationId,
projectId: authCheck.scope.projectId,
},
});
// Validity of id and access checks
if (!item) {
if (!datasetItem) {
console.error("item not found");
return res.status(404).json({
message: "Dataset item not found or not active",
});
}
if (!observation) {
console.error("observation not found");
const observation = observationId
? await prisma.observation.findUnique({
where: {
id: observationId,
projectId: authCheck.scope.projectId,
},
})
: undefined;
if (observationId && !observation) {
console.error("Observation not found");
return res.status(404).json({
message: "Observation not found",
});
}
const trace = traceId
? await prisma.trace.findUnique({
where: { id: traceId, projectId: authCheck.scope.projectId },
})
: undefined;
if (traceId && !trace) {
console.error("Trace not found");
return res.status(404).json({
message: "Trace not found",
});
}
// double check, should not be necessary due to zod schema + validations above
const saveTraceId = trace?.id ?? observation?.traceId;
if (!!!saveTraceId) {
console.error("Observation or Trace not found");
return res.status(404).json({
message: "Observation or Trace not found",
});
}
const run = await prisma.datasetRuns.upsert({
where: {
datasetId_name: {
datasetId: item.datasetId,
datasetId: datasetItem.datasetId,
name: runName,
},
},
create: {
name: runName,
datasetId: item.datasetId,
description: runDescription ?? undefined,
datasetId: datasetItem.datasetId,
metadata: metadata ?? undefined,
},
update: {
metadata: metadata ?? undefined,
description: runDescription ?? undefined,
},
});
const runItem = await prisma.datasetRunItems.create({
data: {
datasetItemId: datasetItemId,
observationId: observationId,
traceId: saveTraceId,
observationId: observation?.id ?? undefined,
datasetRunId: run.id,
},
});
+87 -5
View File
@@ -4,9 +4,15 @@ import { cors, runMiddleware } from "@/src/features/public-api/server/cors";
import { prisma } from "@langfuse/shared/src/db";
import { verifyAuthHeaderAndReturnScope } from "@/src/features/public-api/server/apiAuth";
import { isPrismaException } from "@/src/utils/exceptions";
import { paginationZod } from "@/src/utils/zod";
const CreateDatasetSchema = z.object({
name: z.string(),
description: z.string().nullish(),
});
const GetDatasetsSchema = z.object({
...paginationZod,
});
export default async function handler(
@@ -34,13 +40,14 @@ export default async function handler(
JSON.stringify(req.body, null, 2),
);
const { name } = CreateDatasetSchema.parse(req.body);
const { name, description } = CreateDatasetSchema.parse(req.body);
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all")
return res.status(403).json({
message: "Access denied",
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
const dataset = await prisma.dataset.upsert({
@@ -52,12 +59,87 @@ export default async function handler(
},
create: {
name,
description: description ?? undefined,
projectId: authCheck.scope.projectId,
},
update: {},
update: {
description: description ?? null,
},
});
res.status(200).json({ ...dataset, items: [], runs: [] });
} else if (req.method === "GET") {
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
const args = GetDatasetsSchema.parse(req.query); // uses query and not body
console.log("Trying to get datasets", args);
const datasets = await prisma.dataset.findMany({
select: {
name: true,
description: true,
projectId: true,
createdAt: true,
updatedAt: true,
id: true,
datasetItems: {
select: {
id: true,
},
orderBy: {
createdAt: "desc",
},
},
datasetRuns: {
select: {
name: true,
},
orderBy: {
createdAt: "desc",
},
},
},
where: {
projectId: authCheck.scope.projectId,
},
orderBy: {
createdAt: "desc",
},
take: args.limit,
skip: (args.page - 1) * args.limit,
});
console.log("Found datasets", datasets);
const totalItems = await prisma.dataset.count({
where: {
projectId: authCheck.scope.projectId,
},
});
return res.status(200).json({
data: datasets.map(({ datasetItems, datasetRuns, ...rest }) => ({
...rest,
items: datasetItems.map(({ id }) => id),
runs: datasetRuns.map(({ name }) => name),
})),
meta: {
page: args.page,
limit: args.limit,
totalItems,
totalPages: Math.ceil(totalItems / args.limit),
},
});
} else {
res.status(405).json({
message: "Method Not Allowed",
});
}
} catch (error: unknown) {
console.error(error);
@@ -27,8 +27,7 @@ export default async function handler(
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET scores",
message: "Access denied - need to use basic auth with secret key",
});
}
@@ -30,8 +30,7 @@ export default async function handler(
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET dataset runs",
message: "Access denied - need to use basic auth with secret key",
});
}
console.log(
@@ -54,6 +53,11 @@ export default async function handler(
},
include: {
datasetRunItems: true,
dataset: {
select: {
name: true,
},
},
},
});
@@ -75,7 +79,12 @@ export default async function handler(
message: "Dataset run not found",
});
return res.status(200).json(datasetRuns[0]);
const { dataset, ...run } = datasetRuns[0];
return res.status(200).json({
...run,
datasetName: dataset.name,
});
} catch (error: unknown) {
console.error(error);
if (isPrismaException(error)) {
+1 -2
View File
@@ -33,8 +33,7 @@ export default async function handler(
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET scores",
message: "Access denied - need to use basic auth with secret key",
});
}
const obj = GetUsageSchema.parse(req.query); // uses query and not body
@@ -38,8 +38,7 @@ export default async function handler(
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET traces",
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
@@ -11,6 +11,7 @@ import {
import { type NextApiRequest, type NextApiResponse } from "next";
import { z } from "zod";
import { isPrismaException } from "@/src/utils/exceptions";
import { stringDate } from "@/src/features/public-api/server/ingestion-api-schema";
const ObservationsGetSchema = z.object({
...paginationZod,
@@ -19,6 +20,7 @@ const ObservationsGetSchema = z.object({
userId: z.string().nullish(),
traceId: z.string().nullish(),
parentObservationId: z.string().nullish(),
fromStartTime: stringDate,
});
export default async function handler(
@@ -51,8 +53,7 @@ export default async function handler(
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET generations",
message: "Access denied - need to use basic auth with secret key",
});
}
@@ -120,6 +121,10 @@ const getObservation = async (
? Prisma.sql`AND o."parent_observation_id" = ${query.parentObservationId}`
: Prisma.empty;
const fromStartTimeCondition = query.fromStartTime
? Prisma.sql`AND o."start_time" >= ${query.fromStartTime}::timestamp with time zone at time zone 'UTC'`
: Prisma.empty;
const observations = await prisma.$queryRaw<ObservationView[]>`
SELECT
o."id",
@@ -158,6 +163,7 @@ const getObservation = async (
${observationTypeCondition}
${traceIdCondition}
${parentObservationIdCondition}
${fromStartTimeCondition}
ORDER by o."start_time" DESC
OFFSET ${(query.page - 1) * query.limit}
LIMIT ${query.limit}
@@ -170,6 +176,7 @@ const getObservation = async (
${userIdCondition}
${traceIdCondition}
${parentObservationIdCondition}
${fromStartTimeCondition}
`;
if (count.length !== 1) {
+2 -4
View File
@@ -46,8 +46,7 @@ export default async function handler(
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET prompts",
message: "Access denied - need to use basic auth with secret key",
});
}
@@ -112,8 +111,7 @@ export default async function handler(
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to POST prompts",
message: "Access denied - need to use basic auth with secret key",
});
}
+20 -8
View File
@@ -9,6 +9,7 @@ import {
ScoreBody,
eventTypes,
ingestionBatchEvent,
stringDate,
} from "@/src/features/public-api/server/ingestion-api-schema";
import { v4 } from "uuid";
import {
@@ -21,6 +22,7 @@ const ScoresGetSchema = z.object({
...paginationZod,
userId: z.string().nullish(),
name: z.string().nullish(),
fromTimestamp: stringDate,
});
export default async function handler(
@@ -87,16 +89,22 @@ export default async function handler(
try {
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET scores",
message: "Access denied - need to use basic auth with secret key",
});
}
const obj = ScoresGetSchema.parse(req.query); // uses query and not body
const skipValue = (obj.page - 1) * obj.limit;
const userCondition = Prisma.sql`AND t."user_id" = ${obj.userId}`;
const nameCondition = Prisma.sql`AND s."name" = ${obj.name}`;
const userCondition = obj.userId
? Prisma.sql`AND t."user_id" = ${obj.userId}`
: Prisma.empty;
const nameCondition = obj.name
? Prisma.sql`AND s."name" = ${obj.name}`
: Prisma.empty;
const fromTimestampCondition = obj.fromTimestamp
? Prisma.sql`AND t."timestamp" >= ${obj.fromTimestamp}::timestamp with time zone at time zone 'UTC'`
: Prisma.empty;
const scores = await prisma.$queryRaw<
Array<Score & { trace: { userId: string } }>
@@ -113,17 +121,21 @@ export default async function handler(
FROM "scores" AS s
JOIN "traces" AS t ON t.id = s.trace_id
WHERE t.project_id = ${authCheck.scope.projectId}
${obj.userId ? userCondition : Prisma.empty}
${obj.name ? nameCondition : Prisma.empty}
${userCondition}
${nameCondition}
${fromTimestampCondition}
ORDER BY t."timestamp" DESC
LIMIT ${obj.limit} OFFSET ${skipValue}
`);
const totalItems = await prisma.score.count({
where: {
name: obj.name ?? undefined, // optional filter
name: obj.name ? obj.name : undefined,
timestamp: obj.fromTimestamp
? { gte: new Date(obj.fromTimestamp) }
: undefined,
trace: {
projectId: authCheck.scope.projectId,
userId: obj.userId ?? undefined, // optional filter
userId: obj.userId ? obj.userId : undefined,
},
},
});
+2 -4
View File
@@ -28,8 +28,7 @@ export default async function handler(
// END CHECK AUTH
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to DELETE scores",
message: "Access denied - need to use basic auth with secret key",
});
}
@@ -100,8 +99,7 @@ export default async function handler(
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET scores",
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
@@ -37,8 +37,7 @@ export default async function handler(
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET traces",
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
+21 -7
View File
@@ -12,6 +12,7 @@ import {
import {
TraceBody,
eventTypes,
stringDate,
} from "@/src/features/public-api/server/ingestion-api-schema";
import { v4 } from "uuid";
import { telemetry } from "@/src/features/telemetry";
@@ -25,6 +26,7 @@ const GetTracesSchema = z.object({
userId: z.string().nullish(),
name: z.string().nullish(),
tags: z.union([z.array(z.string()), z.string()]).nullish(),
fromTimestamp: stringDate,
orderBy: z
.string() // orderBy=timestamp.asc
.nullish()
@@ -61,10 +63,11 @@ export default async function handler(
JSON.stringify(req.body, null, 2),
);
if (authCheck.scope.accessLevel !== "all")
return res.status(403).json({
message: "Access denied",
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message: "Access denied - need to use basic auth with secret key",
});
}
const body = TraceBody.parse(req.body);
@@ -82,8 +85,7 @@ export default async function handler(
} else if (req.method === "GET") {
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET scores",
message: "Access denied - need to use basic auth with secret key",
});
}
@@ -104,6 +106,9 @@ export default async function handler(
", ",
)}] <@ t."tags"`
: Prisma.empty;
const fromTimestampCondition = obj.fromTimestamp
? Prisma.sql`AND t."timestamp" >= ${obj.fromTimestamp}::timestamp with time zone at time zone 'UTC'`
: Prisma.empty;
const orderByCondition = orderByToPrismaSql(
obj.orderBy ?? null,
@@ -140,6 +145,7 @@ export default async function handler(
${userCondition}
${nameCondition}
${tagsCondition}
${fromTimestampCondition}
GROUP BY t.id
${orderByCondition}
LIMIT ${obj.limit} OFFSET ${skipValue}
@@ -147,8 +153,16 @@ export default async function handler(
const totalItems = await prisma.trace.count({
where: {
projectId: authCheck.scope.projectId,
name: obj.name ?? undefined,
userId: obj.userId ?? undefined,
name: obj.name ? obj.name : undefined,
userId: obj.userId ? obj.userId : undefined,
timestamp: obj.fromTimestamp
? { gte: new Date(obj.fromTimestamp) }
: undefined,
tags: obj.tags
? {
hasEvery: Array.isArray(obj.tags) ? obj.tags : [obj.tags],
}
: undefined,
},
});
+1 -2
View File
@@ -38,8 +38,7 @@ export default async function handler(
// CHECK ACCESS SCOPE
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET traces",
message: "Access denied - need to use basic auth with secret key",
});
}
// END CHECK ACCESS SCOPE
+1 -2
View File
@@ -30,8 +30,7 @@ export default async function handler(
if (req.method === "GET") {
if (authCheck.scope.accessLevel !== "all") {
return res.status(401).json({
message:
"Access denied - need to use basic auth with secret key to GET scores",
message: "Access denied - need to use basic auth with secret key",
});
}
+46 -9
View File
@@ -14,6 +14,7 @@ import { env } from "@/src/env.mjs";
import { zodResolver } from "@hookform/resolvers/zod";
import { FcGoogle } from "react-icons/fc";
import { FaGithub } from "react-icons/fa";
import { SiOkta, SiAuth0 } from "react-icons/si";
import { TbBrandAzure } from "react-icons/tb";
import { signIn } from "next-auth/react";
import Head from "next/head";
@@ -41,7 +42,9 @@ export type PageProps = {
credentials: boolean;
google: boolean;
github: boolean;
okta: boolean;
azureAd: boolean;
auth0: boolean;
};
};
@@ -57,11 +60,19 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async () => {
github:
env.AUTH_GITHUB_CLIENT_ID !== undefined &&
env.AUTH_GITHUB_CLIENT_SECRET !== undefined,
okta:
env.AUTH_OKTA_CLIENT_ID !== undefined &&
env.AUTH_OKTA_CLIENT_SECRET !== undefined &&
env.AUTH_OKTA_ISSUER !== undefined,
credentials: env.AUTH_DISABLE_USERNAME_PASSWORD !== "true",
azureAd:
env.AUTH_AZURE_AD_CLIENT_ID !== undefined &&
env.AUTH_AZURE_AD_CLIENT_SECRET !== undefined &&
env.AUTH_AZURE_AD_TENANT_ID !== undefined,
auth0:
env.AUTH_AUTH0_CLIENT_ID !== undefined &&
env.AUTH_AUTH0_CLIENT_SECRET !== undefined &&
env.AUTH_AUTH0_ISSUER !== undefined,
},
},
};
@@ -118,6 +129,30 @@ export function SSOButtons({
{action} with Azure AD
</Button>
)}
{authProviders.okta && (
<Button
onClick={() => {
posthog.capture("sign_in:okta_button_click");
void signIn("okta");
}}
variant="secondary"
>
<SiOkta className="mr-3" size={18} />
{action} with Okta
</Button>
)}
{authProviders.auth0 && (
<Button
onClick={() => {
posthog.capture("sign_in:auth0_button_click");
void signIn("auth0");
}}
variant="secondary"
>
<SiAuth0 className="mr-3" size={18} />
{action} with Auth0
</Button>
)}
</div>
</div>
) : null
@@ -131,7 +166,10 @@ export default function SignIn({ authProviders }: PageProps) {
const posthog = usePostHog();
const [turnstileToken, setTurnstileToken] = useState<string>();
const turnstileRef = useRef<any>();
// Used to refresh turnstile as the token can only be used once
const [turnstileCData, setTurnstileCData] = useState<string>(
new Date().getTime().toString(),
);
// Credentials
const credentialsForm = useForm<z.infer<typeof credentialAuthForm>>({
@@ -157,12 +195,8 @@ export default function SignIn({ authProviders }: PageProps) {
setCredentialsFormError(result.error);
// Refresh turnstile as the token can only be used once
if (
env.NEXT_PUBLIC_TURNSTILE_SITE_KEY &&
turnstileToken &&
turnstileRef.current
) {
turnstileRef.current?.reset();
if (env.NEXT_PUBLIC_TURNSTILE_SITE_KEY && turnstileToken) {
setTurnstileCData(new Date().getTime().toString());
setTurnstileToken(undefined);
}
}
@@ -240,9 +274,12 @@ export default function SignIn({ authProviders }: PageProps) {
<>
<Divider className="text-gray-400" />
<Turnstile
ref={turnstileRef}
siteKey={env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
options={{ theme: "light", action: "sign-in" }}
options={{
theme: "light",
action: "sign-in",
cData: turnstileCData,
}}
className="mx-auto"
onSuccess={setTurnstileToken}
/>
@@ -17,11 +17,7 @@ export default function Traces() {
href: "https://langfuse.com/docs/datasets",
}}
actionButtons={
<DatasetActionButton
projectId={projectId}
className="mt-4"
mode="create"
/>
<DatasetActionButton projectId={projectId} mode="create" />
}
/>
<DatasetsTable projectId={projectId} />
@@ -22,11 +22,18 @@ export default function Dataset() {
return (
<div>
<Header
title={`Dataset: ${dataset.data?.name}`}
title={dataset.data?.name ?? ""}
breadcrumb={[
{ name: "Datasets", href: `/project/${projectId}/datasets` },
{ name: dataset.data?.name ?? datasetId },
]}
help={
dataset.data?.description
? {
description: dataset.data.description,
}
: undefined
}
actionButtons={
<>
<DetailPageNav
@@ -35,10 +42,11 @@ export default function Dataset() {
listKey="datasets"
/>
<DatasetActionButton
mode="rename"
mode="update"
projectId={projectId}
datasetId={datasetId}
datasetName={dataset.data?.name ?? ""}
datasetDescription={dataset.data?.description ?? undefined}
icon
/>
<DeleteButton
@@ -7,6 +7,7 @@ import { DatasetItemsTable } from "@/src/features/datasets/components/DatasetIte
import { DetailPageNav } from "@/src/features/navigate-detail-pages/DetailPageNav";
import { DatasetActionButton } from "@/src/features/datasets/components/DatasetActionButton";
import { DeleteButton } from "@/src/components/deleteButton";
import { NewDatasetItemButton } from "@/src/features/datasets/components/NewDatasetItemButton";
export default function DatasetItems() {
const router = useRouter();
@@ -22,7 +23,14 @@ export default function DatasetItems() {
return (
<div>
<Header
title={`Dataset: ${dataset.data?.name}`}
title={dataset.data?.name ?? ""}
help={
dataset.data?.description
? {
description: dataset.data.description,
}
: undefined
}
breadcrumb={[
{ name: "Datasets", href: `/project/${projectId}/datasets` },
{
@@ -35,16 +43,18 @@ export default function DatasetItems() {
]}
actionButtons={
<>
<NewDatasetItemButton projectId={projectId} datasetId={datasetId} />
<DetailPageNav
currentId={datasetId}
path={(id) => `/project/${projectId}/datasets/${id}/items/`}
listKey="datasets"
/>
<DatasetActionButton
mode="rename"
mode="update"
projectId={projectId}
datasetId={datasetId}
datasetName={dataset.data?.name ?? ""}
datasetDescription={dataset.data?.description ?? undefined}
icon
/>
<DeleteButton
@@ -44,6 +44,12 @@ export default function Dataset() {
/>
}
/>
{!!run.data?.description && (
<>
<Header title="Description" level="h3" />
<JSONView json={run.data.description} />
</>
)}
{!!run.data?.metadata && (
<>
<Header title="Metadata" level="h3" />
+1 -1
View File
@@ -218,7 +218,7 @@ export default function Start() {
globalFilterState={mergedFilterState}
/>
<GenerationLatencyChart
className="col-span-1 flex-auto justify-between xl:col-span-full"
className="col-span-1 flex-auto justify-between lg:col-span-full"
projectId={projectId}
agg={agg}
globalFilterState={mergedFilterState}
+3 -1
View File
@@ -227,7 +227,9 @@ export default function UsersPage() {
firstEvent:
t.firstTrace?.toLocaleString() ?? "No event yet",
lastEvent:
t.lastObservation?.toLocaleString() ?? "No event yet",
t.lastObservation?.toLocaleString() ??
t.lastTrace?.toLocaleString() ??
"No event yet",
totalEvents: compactNumberFormatter(
(Number(t.totalTraces) || 0) +
(Number(t.totalObservations) || 0),
+1 -1
View File
@@ -48,7 +48,7 @@ export const scoresRouter = createTRPCRouter({
);
const scores = await ctx.prisma.$queryRaw<
Array<Score & { traceName: string; userId: string | null }>
Array<Score & { traceName: string | null; userId: string | null }>
>(
generateScoresQuery(
Prisma.sql`
+18 -2
View File
@@ -34,6 +34,7 @@ const TraceFilterOptions = z.object({
searchQuery: z.string().nullable(),
filter: z.array(singleFilter).nullable(),
orderBy: orderBy,
returnIO: z.boolean().default(true),
...paginationZod,
});
@@ -48,6 +49,7 @@ export const traceRouter = createTRPCRouter({
all: protectedProjectProcedure
.input(TraceFilterOptions)
.query(async ({ input, ctx }) => {
const returnIO = input.returnIO;
const filterCondition = tableColumnsToSqlFilterAndPrefix(
input.filter ?? [],
tracesTableCols,
@@ -155,7 +157,17 @@ export const traceRouter = createTRPCRouter({
return {
traces: traces.map((trace) => {
const filteredScores = scores.filter((s) => s.traceId === trace.id);
return { ...trace, scores: filteredScores };
const { input, output, ...rest } = trace;
if (returnIO) {
return { ...rest, input, output, scores: filteredScores };
} else {
return {
...rest,
input: undefined,
output: undefined,
scores: filteredScores,
};
}
}),
totalCount: totalTraceCount ? Number(totalTraceCount) : undefined,
};
@@ -209,7 +221,11 @@ export const traceRouter = createTRPCRouter({
return res;
}),
byId: protectedGetTraceProcedure
.input(z.object({ traceId: z.string() }))
.input(
z.object({
traceId: z.string(),
}),
)
.query(async ({ input, ctx }) => {
const trace = await ctx.prisma.trace.findFirstOrThrow({
where: {
+32
View File
@@ -17,6 +17,8 @@ import { type Adapter } from "next-auth/adapters";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
import GitHubProvider from "next-auth/providers/github";
import OktaProvider from "next-auth/providers/okta";
import Auth0Provider from "next-auth/providers/auth0";
import AzureADProvider from "next-auth/providers/azure-ad";
import { type Provider } from "next-auth/providers/index";
import { getCookieName, cookieOptions } from "./utils/cookies";
@@ -110,6 +112,36 @@ if (env.AUTH_GOOGLE_CLIENT_ID && env.AUTH_GOOGLE_CLIENT_SECRET)
}),
);
if (
env.AUTH_OKTA_CLIENT_ID &&
env.AUTH_OKTA_CLIENT_SECRET &&
env.AUTH_OKTA_ISSUER
)
providers.push(
OktaProvider({
clientId: env.AUTH_OKTA_CLIENT_ID,
clientSecret: env.AUTH_OKTA_CLIENT_SECRET,
issuer: env.AUTH_OKTA_ISSUER,
allowDangerousEmailAccountLinking:
env.AUTH_OKTA_ALLOW_ACCOUNT_LINKING === "true",
}),
);
if (
env.AUTH_AUTH0_CLIENT_ID &&
env.AUTH_AUTH0_CLIENT_SECRET &&
env.AUTH_AUTH0_ISSUER
)
providers.push(
Auth0Provider({
clientId: env.AUTH_AUTH0_CLIENT_ID,
clientSecret: env.AUTH_AUTH0_CLIENT_SECRET,
issuer: env.AUTH_AUTH0_ISSUER,
allowDangerousEmailAccountLinking:
env.AUTH_AUTH0_ALLOW_ACCOUNT_LINKING === "true",
}),
);
if (env.AUTH_GITHUB_CLIENT_ID && env.AUTH_GITHUB_CLIENT_SECRET)
providers.push(
GitHubProvider({
+6 -6
View File
@@ -20,17 +20,17 @@
"author": "engineering@langfuse.com",
"dependencies": {
"@langfuse/shared": "workspace:*",
"@sentry/node": "^7.107.0",
"@sentry/profiling-node": "^7.102.1",
"@sentry/node": "^7.109.0",
"@sentry/profiling-node": "^7.109.0",
"backoff": "^2.5.0",
"bullmq": "^5.4.2",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"express": "^4.19.2",
"ioredis": "^5.3.2",
"kysely": "^0.27.3",
"lodash": "^4.17.21",
"pg": "^8.11.3",
"pg": "^8.11.5",
"pino": "^8.19.0",
"pino-http": "^9.0.0",
"pino-pretty": "^10.3.1",
@@ -45,7 +45,7 @@
"@types/express-serve-static-core": "^4.17.43",
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.19",
"@types/pg": "^8.11.2",
"@types/pg": "^8.11.4",
"@typescript-eslint/parser": "^7.2.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
@@ -57,7 +57,7 @@
"ts-node": "^10.9.2",
"tsc-watch": "^6.0.4",
"tsup": "^8.0.2",
"typescript": "^5.3.3",
"typescript": "^5.4.4",
"vitest": "^1.3.1"
}
}