Compare commits

...
206 Commits
Author SHA1 Message Date
Max Deichmann ad10e6f76f chore: release v2.40.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-05-14 18:03:59 +02:00
Hassieb PakzadandGitHub 1f9adec948 feat: adds gpt-4o model to evals (#2052) 2024-05-14 18:03:13 +02:00
Hassieb PakzadandGitHub a158d6da09 feat(playground): adds gpt-4o models to playground (#2051) 2024-05-14 10:40:14 +02:00
Marc Klingen e6f9e5e06d chore: release v2.39.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-05-14 01:00:41 +02:00
Marc KlingenandGitHub c2463cb91e feat: add tokenization for gpt4o (#2048) 2024-05-14 00:58:22 +02:00
Marc KlingenandGitHub 38e472ec66 feat: add pricing information for gpt4o (#2047)
add pricing information for gpt4o
Fixes #2046
2024-05-13 22:05:18 +00:00
616e79238b feat: add env LANGFUSE_DEFAULT_TABLE_DATETIME_OFFSET (#2040)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-05-13 16:24:25 +02:00
Marc KlingenandGitHub e2a59a2639 ci: fix tests (#2041) 2024-05-13 13:52:45 +00:00
Marc KlingenandGitHub 07609ade4f refactor: rename memberships to projectMemberships and enum MembershipRole to ProjectRole (#2037) 2024-05-12 19:06:50 +02:00
Marc Klingen 6627c59db4 chore: remove unused worder/generated/types, monorepo cleanup 2024-05-12 19:01:11 +02:00
Marc Klingen 70e1426790 fix: seeder for priojectId on scores 2024-05-10 15:17:03 +02:00
Marc KlingenandGitHub 8550d7313f chore: backfill projectid on score (#2011) 2024-05-09 20:29:43 +02:00
Marc KlingenandGitHub 1d76ef3a18 perf: add index to projectid on scores (#2028) 2024-05-09 18:03:33 +02:00
Marc Klingen b68d15309b docs: update contributing 2024-05-09 17:50:11 +02:00
Marc KlingenandGitHub c7b3e95645 feat(scores): add nullable projectId to scores (#2010) 2024-05-09 17:47:06 +02:00
Marc KlingenandGitHub 2c55cd842a fix(ui): scroll dataset item editor on large inputs (#2027) 2024-05-09 14:39:08 +00:00
Marc Klingen dc4e217a6e chore: release v2.38.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-05-09 15:39:47 +02:00
Marc KlingenandGitHub 90f89f8b33 feat: add optional LANGFUSE_DISABLE_EXPENSIVE_POSTGRES_QUERIES (#2026) 2024-05-09 15:38:32 +02:00
Marc KlingenandGitHub 2cbcf1fc02 perf: move client side envs to session instead of fetching them via trpc (#2025) 2024-05-09 13:19:29 +00:00
Marc KlingenandGitHub 04251e2451 fix: navigation after deleting project (#2022) 2024-05-09 12:39:55 +00:00
Hassieb PakzadandGitHub b3ff1e0bd1 fix: make latest label a reserved label (#2019) 2024-05-09 11:19:45 +02:00
Max Deichmann 9fc1f79b94 chore: release v2.38.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-05-08 17:42:34 +02:00
Marc Klingen 06bf8302d6 fix: notification version 2024-05-08 01:00:51 +02:00
7acc8e1f4d feat: add frontend posthog events (#1983)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-05-08 00:59:12 +02:00
Max DeichmannandGitHub b88809f7e5 perf: add sentry metrics integration (#2002) 2024-05-07 14:15:53 +00:00
Hassieb PakzadandGitHub 7ee5c2d573 fix(ui): render chatml output correctly if array passed (#2000) 2024-05-07 13:40:55 +00:00
Max DeichmannandGitHub 702a591a0d perf: add sentry counter for ingested events (#1999) 2024-05-07 13:34:45 +00:00
Marc KlingenandGitHub 6021ef94c8 fix(rbac): viewers have read-only access to evaluation resources (#1997) 2024-05-07 13:24:19 +00:00
Marc KlingenandGitHub d2d10893e1 fix: help strings and links across eval feature (#1996) 2024-05-07 13:09:05 +00:00
Max DeichmannandGitHub dd78b9be7e perf: instrument tokenisation (#1995) 2024-05-07 12:54:39 +00:00
Max DeichmannandGitHub ec1a9c53e7 fix: improve filteroptions api (#1993) 2024-05-07 12:13:35 +00:00
Max DeichmannandGitHub 74da65f9b2 perf: limit filteroptions api (#1973) 2024-05-07 14:05:19 +02:00
654620c336 fix(prompts): filter by label to prompts table (#1980)
* fix(prompts): add filter by label to prompts table

* feat: adds label filter to prompt table

---------

Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com>
2024-05-07 10:42:27 +02:00
Marc Klingen d1b935b280 docs: update api reference to specify datetime format (ISO 8601) 2024-05-06 18:10:01 +02:00
Max DeichmannandGitHub 7dfa914934 fix: validate query (#1985) 2024-05-06 15:17:09 +00:00
ClemoandGitHub a126b05bb9 fix: update entity name from finto technologies gmbh to langfuse gmbh 2024-05-06 17:01:01 +02:00
ClemoandGitHub b81a81d76f docs: add /ee license (enterprise edition) 2024-05-06 17:00:05 +02:00
Marc Klingen 15014eb8ea chore: release v2.38.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-05-05 21:32:51 +02:00
Marc KlingenandGitHub e1fb0e4102 perf: exclude observation metadata in traces.byId to prevent OOM when viewing large traces (#1978) 2024-05-05 19:47:35 +02:00
Marc Klingen c7d99476eb chore(cloud): update terms and conditions link 2024-05-05 17:51:42 +02:00
Marc KlingenandGitHub 7e3980856c feat(ui): add container queries for embedded tables + accordion to hide generations on prompt versions (#1974) 2024-05-03 21:17:51 +00:00
marliessophieandGitHub 54991e2246 feat: show generations table per prompt version, add observationCount to traces table (#1971)
* feat: add number of observations as new col on traces

* perf: load `observationCount` directly

* feat: show generations table per prompt version

* feat: remove `isHideIfEmptyEnabled` config

* fix: remove `enableSorting` for `observationCount`
2024-05-03 17:36:00 +00:00
Marc KlingenandGitHub 10bbfd75c3 chore(db): add cursors to trace table (#1968) 2024-05-03 15:48:43 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4476d6c1e7 chore(deps): bump the prisma group with 2 updates (#1959)
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.12.1 to 5.13.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.13.0/packages/cli)

Updates `@prisma/client` from 5.12.1 to 5.13.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.13.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-05-03 12:18:44 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9dc758693b chore(deps): bump @aws-sdk/lib-storage from 3.550.0 to 3.568.0 (#1961)
Bumps [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) from 3.550.0 to 3.568.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.568.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-05-03 12:08:19 +00:00
Marc Klingen 082031ccd2 docs: fix links to published specs in api spec 2024-05-03 14:01:35 +02:00
Marc KlingenandGitHub a88764db7e perf: limit max length of trpc batch requests (#1966) 2024-05-03 13:55:26 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b6e0acb6f3 chore(deps): bump the sentry group with 4 updates (#1958)
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.110.1 to 7.113.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.113.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.110.1...7.113.0)

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

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

Updates `@sentry/types` from 7.110.1 to 7.113.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.113.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.110.1...7.113.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-05-03 09:34:35 +00:00
Max DeichmannandGitHub 366ccde902 chore: improve logging (#1965) 2024-05-03 09:17:36 +00:00
Marc KlingenandGitHub 21c60fab76 fix: generate api spec and postman collection to public folder (#1964)
* fix: generate api spec and postman collection to public folder

* remove copy script from package

* remove old generation target
2024-05-03 08:55:33 +00:00
Max Deichmann 7c5a49b4ee chore: release v2.37.3
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-05-02 17:40:56 +02:00
bfbb83e74f fix: use provided job delay instead of default if provided (#1950) (#1955)
Co-authored-by: Tim Beyer <tim.beyer@gmail.com>
2024-05-02 17:39:42 +02:00
Marc KlingenandGitHub 8cde89689f chore: Update bug_report.yml 2024-05-02 12:24:30 +02:00
Marc KlingenandGitHub 823d94e6fd chore: update issue template 2024-05-02 12:23:53 +02:00
Marc Klingen 2bbb88ce93 chore: update issue template 2024-05-02 12:07:53 +02:00
52cfba89e8 feat(auth): redirect to targetPath after login (#1913)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-05-02 11:47:41 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2de3d6fe8f chore(deps): bump the patches group across 1 directory with 14 updates (#1944)
Bumps the patches group with 14 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [release-it](https://github.com/release-it/release-it) | `17.2.0` | `17.2.1` |
| [turbo](https://github.com/vercel/turbo) | `1.13.2` | `1.13.3` |
| [@langchain/anthropic](https://github.com/langchain-ai/langchainjs) | `0.1.14` | `0.1.16` |
| [@langchain/core](https://github.com/langchain-ai/langchainjs) | `0.1.58` | `0.1.61` |
| [@react-email/components](https://github.com/resend/react-email/tree/HEAD/packages/components) | `0.0.16` | `0.0.17` |
| [@react-email/render](https://github.com/resend/react-email/tree/HEAD/packages/render) | `0.0.12` | `0.0.13` |
| [clsx](https://github.com/lukeed/clsx) | `2.1.0` | `2.1.1` |
| [langchain](https://github.com/langchain-ai/langchainjs) | `0.1.33` | `0.1.36` |
| [next](https://github.com/vercel/next.js) | `14.2.1` | `14.2.3` |
| [@testing-library/react](https://github.com/testing-library/react-testing-library) | `15.0.2` | `15.0.6` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `14.2.1` | `14.2.3` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `5.2.9` | `5.2.10` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `1.5.0` | `1.5.3` |
| [eslint-config-turbo](https://github.com/vercel/turbo/tree/HEAD/packages/eslint-config-turbo) | `1.13.2` | `1.13.3` |



Updates `release-it` from 17.2.0 to 17.2.1
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/main/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/17.2.0...17.2.1)

Updates `turbo` from 1.13.2 to 1.13.3
- [Release notes](https://github.com/vercel/turbo/releases)
- [Changelog](https://github.com/vercel/turbo/blob/main/release.md)
- [Commits](https://github.com/vercel/turbo/compare/v1.13.2...v1.13.3)

Updates `@langchain/anthropic` from 0.1.14 to 0.1.16
- [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.14...0.1.16)

Updates `@langchain/core` from 0.1.58 to 0.1.61
- [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 `@react-email/components` from 0.0.16 to 0.0.17
- [Release notes](https://github.com/resend/react-email/releases)
- [Commits](https://github.com/resend/react-email/commits/@react-email/components@0.0.17/packages/components)

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

Updates `clsx` from 2.1.0 to 2.1.1
- [Release notes](https://github.com/lukeed/clsx/releases)
- [Commits](https://github.com/lukeed/clsx/compare/v2.1.0...v2.1.1)

Updates `langchain` from 0.1.33 to 0.1.36
- [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.33...0.1.36)

Updates `next` from 14.2.1 to 14.2.3
- [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.2.1...v14.2.3)

Updates `@testing-library/react` from 15.0.2 to 15.0.6
- [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/v15.0.2...v15.0.6)

Updates `eslint-config-next` from 14.2.1 to 14.2.3
- [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.2.3/packages/eslint-config-next)

Updates `vite` from 5.2.9 to 5.2.10
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.10/packages/vite)

Updates `vitest` from 1.5.0 to 1.5.3
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.5.3/packages/vitest)

Updates `eslint-config-turbo` from 1.13.2 to 1.13.3
- [Release notes](https://github.com/vercel/turbo/releases)
- [Changelog](https://github.com/vercel/turbo/blob/main/release.md)
- [Commits](https://github.com/vercel/turbo/commits/v1.13.3/packages/eslint-config-turbo)

---
updated-dependencies:
- dependency-name: release-it
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: turbo
  dependency-type: direct:development
  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: "@react-email/components"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: "@react-email/render"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: clsx
  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: next
  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: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: vitest
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: eslint-config-turbo
  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-05-02 10:57:20 +02:00
Max DeichmannandGitHub f873a7dc97 chore: improve logging (#1942) 2024-05-01 15:00:58 +00:00
Max DeichmannandGitHub 340e8927bd chore: move worker logs to debug (#1939) 2024-05-01 13:50:37 +00:00
Max DeichmannandGitHub cf0ea0f9fb chore: sentry worker global sampling (#1938) 2024-05-01 13:45:25 +00:00
Max DeichmannandGitHub d46715ff79 chore: reduce logging again (#1937) 2024-05-01 13:37:49 +00:00
Max DeichmannandGitHub 73d06086b6 fix: worker tsconfig (#1936) 2024-05-01 13:31:50 +00:00
Max DeichmannandGitHub e728a73f29 chore: reduce logging (#1935) 2024-05-01 12:56:45 +00:00
Max DeichmannandGitHub ec3c946965 perf: use prisma joins (#1934) 2024-05-01 11:23:07 +00:00
Max Deichmann a4eedbaba3 chore: release v2.37.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-05-01 13:00:20 +02:00
Max DeichmannandGitHub 32de95648c perf: remove read operation from events table (#1933) 2024-05-01 10:30:46 +00:00
Marc KlingenandGitHub 15c97956bf ui: move new prompt to datatable toolbar (#1929) 2024-04-30 21:32:14 +00:00
Marc KlingenandGitHub debde9c5d3 fix: openapi spec publishing and add postman collection (#1928) 2024-04-30 23:15:33 +02:00
Hassieb PakzadandGitHub 0f78cea5ce fix: add labels to fern definition for prompt return types (#1923) 2024-04-30 19:21:46 +02:00
Marc KlingenandGitHub a3a25425c0 docs: update contributing 2024-04-30 14:27:32 +02:00
Marc KlingenandGitHub 489e812fe9 docs: update CONTRIBUTING.md 2024-04-30 14:09:27 +02:00
Marc Klingen 42589e31ff docs: add descriptions to new v2 prompt api params 2024-04-30 00:43:36 +02:00
Max Deichmann 0cf58be012 chore: release v2.37.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-29 21:07:37 +02:00
Max DeichmannandGitHub c338db5a4d fix: deduplicate events on worker ingestion (#1908) 2024-04-29 19:01:48 +00:00
Max Deichmann 6b22c19ce1 chore: release v2.37.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-29 20:35:34 +02:00
Hassieb PakzadandGitHub a9cac5d6d2 chore: adds mig for latest tag on prompts (#1907) 2024-04-29 20:28:12 +02:00
Hassieb PakzadandGitHub fb1c22896b feat: adds public/v2/prompts (#1846) 2024-04-29 19:44:58 +02:00
Max DeichmannandGitHub 8cb9d5fb60 chore: reduce logging noise (#1903) 2024-04-29 13:33:49 +00:00
Max DeichmannandGitHub c027575dc1 docs: update chart in contributing (#1901) 2024-04-29 14:44:34 +02:00
Marc KlingenandGitHub 870610b575 fix: render layout/menu also when user has no project yet, otherwise cannot sign out (#1900)
Fixes #1787
2024-04-29 10:33:24 +00:00
Max Deichmann 5547e5cc88 chore: release v2.36.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-29 12:22:16 +02:00
Max DeichmannandGitHub dd4a92f9d8 fix: correctly filter prompt on production promotion (#1899) 2024-04-29 10:21:08 +00:00
Max DeichmannandGitHub 9813cd27e0 perf: reduce sentry sampling (#1897) 2024-04-29 08:11:22 +00:00
Marc Klingen b4c636d430 docs: add ENABLE_EVENT_LOG to example env 2024-04-28 12:12:54 +02:00
Marc Klingen c54ca57f6f chore: release v2.36.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-28 12:09:14 +02:00
Marc KlingenandGitHub 3a40df5daa feat: Add ENABLE_EVENT_LOG flag (#1894)
Fixes #1717
2024-04-28 10:07:26 +00:00
Marc KlingenandGitHub 528f1eb737 docs: fix broken link in readme 2024-04-28 00:16:19 +02:00
Marc Klingen 579f00c8fe fix: role MEMBER should have read access to last chars of keys 2024-04-27 22:42:43 +02:00
Marc Klingen f4e8a61e7b fix: typo 2024-04-27 22:36:48 +02:00
Marc KlingenandGitHub 085e669c2c docs: update readme to include video and latest features 2024-04-27 22:34:42 +02:00
Ikko Eltociear AshimineandGitHub a5321fb965 docs: update CONTRIBUTING.md (#1892)
minor fix
2024-04-27 20:09:19 +02:00
Shixian ShengandGitHub 0c7a11e698 docs: update readme (#1891) 2024-04-27 19:07:27 +02:00
Marc Klingen 0ae42e3ff1 chore: remove unused import 2024-04-27 14:47:14 +02:00
Marc Klingen 0c27f41084 chore: remove PH banner 2024-04-27 14:46:18 +02:00
Max Deichmann 2722966892 chore: release v2.35.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-27 14:26:59 +02:00
Max DeichmannandGitHub 31a2fc421d fix: visibility of llm template (#1890) 2024-04-27 12:21:59 +00:00
Max DeichmannandGitHub eb03ffaf85 fix: increase eval attempts (#1889) 2024-04-27 12:16:34 +00:00
Max DeichmannandGitHub 08b04811f1 chore: add eval tests (#1888) 2024-04-27 11:02:15 +00:00
Max DeichmannandGitHub fab53a1c3d feat: capture worker errors in sentry (#1885)
feat: capture woerker errors in sentry
2024-04-26 17:06:32 +00:00
Max DeichmannandGitHub 046ad8075d feat: add logging for score creation in eval (#1884) 2024-04-26 16:01:10 +00:00
Max DeichmannandGitHub c8672b57cc fix: fix failing eval filter (#1883) 2024-04-26 17:47:36 +02:00
852a43d844 fix: missing tokenizer model in isTiktokenModel (#1878) (#1882)
Co-authored-by: Thomas Calvet <calvet.thomas@gmail.com>
2024-04-26 14:49:17 +00:00
Max DeichmannandGitHub 4a323b0011 fix: improve error message on worker (#1881) 2024-04-26 14:33:29 +00:00
Max DeichmannandGitHub 4f7e74a1e4 chore: configure eval worker (#1879) 2024-04-26 12:01:44 +00:00
Marc Klingen 27d3bb8a85 chore: temporary ph notification 2024-04-26 11:27:01 +02:00
Max Deichmann 8ee0b03df6 chore: release v2.34.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-26 10:35:17 +02:00
Max DeichmannandGitHub 6ade4c6603 fix: improve eval error handling (#1875) 2024-04-26 10:31:53 +02:00
Max DeichmannandGitHub 9ae0708cca fix: rbac UI for eval templates (#1869) 2024-04-26 06:38:49 +00:00
Max DeichmannandGitHub 1ba027fc2b fix: add logging to ingestion (#1865) 2024-04-25 21:38:08 +00:00
Max DeichmannandGitHub e11d02a861 perf: reduce sentry sampling rate (#1861) 2024-04-25 18:55:38 +00:00
Max DeichmannandGitHub 41cf5acbe3 fix: fix ui in eval form (#1859) 2024-04-25 16:50:11 +00:00
Marc Klingen 95454abe05 chore: empty commit 2024-04-25 17:58:24 +02:00
Max DeichmannandGitHub ee92e9d7fa chore: move evals to ee folder (#1854) 2024-04-25 14:13:32 +00:00
Marc Klingen fa9e90ed75 hotfix: openapi spec not included in public folder during build 2024-04-25 15:47:42 +02:00
Max DeichmannandGitHub f981da3457 perf: sentry sampling worker (#1853) 2024-04-25 12:56:34 +00:00
Max DeichmannandGitHub 384af7b817 chore: improve worker logger for gcp (#1852) 2024-04-25 12:49:44 +00:00
Max DeichmannandGitHub 6f23b34636 feat: add logs to config page (#1850) 2024-04-25 12:25:09 +00:00
Max DeichmannandGitHub 99b0f5b157 feat: add eval to score table (#1847) 2024-04-25 12:19:15 +00:00
Max DeichmannandGitHub 96cb57871d feat: add toolbar to log table (#1849) 2024-04-25 12:00:04 +00:00
Max DeichmannandGitHub e49ba5448e perf: adjust sampling for trpc (#1848) 2024-04-25 11:53:46 +00:00
Max DeichmannandGitHub ef8e312f48 feat: link eval templates in configs (#1845) 2024-04-25 10:08:13 +00:00
Max DeichmannandGitHub e79553cd03 fix: eval config delay (#1844) 2024-04-25 09:53:56 +00:00
Max DeichmannandGitHub 105db18bc9 feat: improve eval config variable mapping UX (#1839) 2024-04-25 09:41:00 +00:00
Max DeichmannandGitHub dcba65d49e feat: add trpc sentry middleware (#1842) 2024-04-25 10:54:32 +02:00
Max DeichmannandGitHub cf96ed67cc fix: fix playground header link (#1840) 2024-04-24 20:13:34 +00:00
Max DeichmannandGitHub 930d07e4da chore: update in-app notification (#1838) 2024-04-24 16:30:31 +00:00
Max DeichmannandGitHub 50199db733 feat: improve readability from log table (#1837) 2024-04-24 15:55:11 +00:00
Max DeichmannandGitHub 3e65599966 feat: improve user facing errors (#1836) 2024-04-24 15:45:49 +00:00
Max DeichmannandGitHub f2c527a677 perf: improve log table performance (#1835) 2024-04-24 17:19:07 +02:00
Max Deichmann dcf91a112c chore: release v2.33.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-24 16:38:24 +02:00
Max DeichmannandGitHub 5f5449bd78 feat: add eval log (#1832) 2024-04-24 14:37:37 +00:00
Max DeichmannandGitHub e9e6802ba4 feat: improve api key design (#1834) 2024-04-24 15:42:18 +02:00
Marc Klingen e5261ac73d fix: links to docs 2024-04-24 15:01:52 +02:00
Max DeichmannandGitHub 0781c6cd4b fix: form error on missing api keys (#1828) 2024-04-24 11:27:00 +02:00
Max DeichmannandGitHub e708318558 fix: improve error logs (#1827) 2024-04-24 11:21:24 +02:00
Max Deichmann 0f0af537ae chore: release v2.32.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-24 10:24:00 +02:00
Max DeichmannandGitHub 2ae694625d feat: add langfuse managed eval prompt templates (#1815) 2024-04-24 08:21:50 +00:00
Max DeichmannandGitHub b53ad98f50 fix: return if betterstack env is not set (#1825) 2024-04-24 07:50:27 +00:00
Max DeichmannandGitHub 10c0b18ca0 fix: exhaustive check for lllm model provider (#1819) 2024-04-23 18:24:03 +00:00
Max DeichmannandGitHub e0e346dccb feat: bring your own llm api key (#1796) 2024-04-23 18:06:33 +00:00
Max DeichmannandGitHub dec0c5133a bug: revert historic model update (#1817) 2024-04-23 15:18:42 +00:00
Max DeichmannandGitHub 7c72aa16c0 feat: update existing openai models (#1808) 2024-04-23 14:35:48 +00:00
Max DeichmannandGitHub 373ee6dda0 feat: update openai models (#1807) 2024-04-23 14:29:55 +00:00
Marc KlingenandGitHub 2880bac4eb fix: trace-level scores did not show on dataset runs table (#1810) 2024-04-23 13:23:32 +02:00
Max DeichmannandGitHub b28679112a perf: do not try ot match models without user provided model (#1809) 2024-04-23 12:25:30 +02:00
Max DeichmannandGitHub 7a22b0c3bf feat: update model match algo to support larger updates (#1806)
push
2024-04-23 09:52:49 +00:00
Max Deichmann 711f32d3f6 chore: release v2.31.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-23 10:15:45 +02:00
Hassieb PakzadandGitHub 6161d1cf71 feat: launches playground (#1799) 2024-04-23 10:13:29 +02:00
Hassieb PakzadandGitHub aeec1ebe79 refactor: moves playground to enterprise edition (#1797) 2024-04-22 14:59:23 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e9a3e3c9ef chore(deps): bump ioredis from 5.3.2 to 5.4.1 (#1761)
Bumps [ioredis](https://github.com/luin/ioredis) from 5.3.2 to 5.4.1.
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/luin/ioredis/compare/v5.3.2...v5.4.1)

---
updated-dependencies:
- dependency-name: ioredis
  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-22 08:28:55 +00:00
Marc Klingen 2d5b3e2076 chore: add created at to posthog integration table 2024-04-20 15:43:23 +02:00
Marc Klingen aeb15b80fe fix(ui): initial loading state of posthog integration 2024-04-19 21:05:09 +02:00
Marc Klingen c254a36eb5 fix: error on prompts page when author was removed from project 2024-04-19 20:55:56 +02:00
Marc Klingen 6b25d3c544 chore: remove unused imports 2024-04-19 20:53:56 +02:00
Marc Klingen 579c391b26 fix: rbac role admin 2024-04-19 18:28:51 +02:00
Marc Klingen 8584f93307 chore: release v2.30.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-19 18:05:17 +02:00
Marc KlingenandGitHub 08e576f815 feat(cloud): add settings for posthog integration (#1778) 2024-04-19 17:38:55 +02:00
Marc KlingenandGitHub f64da44b20 chore: update docs links in settings (#1786) 2024-04-19 10:49:26 +00:00
Marc KlingenandGitHub c720a4d896 perf: reduce memory usage of users.all trpc route (#1784) 2024-04-19 10:03:06 +00:00
Marc Klingen b59b61a5cf chore: release v2.30.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-19 01:07:09 +02:00
Marc KlingenandGitHub 3f18e42fef feat(api): add countObservations to metrics endpoint, model nullable (#1779) 2024-04-18 23:04:29 +00:00
Marc KlingenandGitHub 31a9be6f1d fix(api): support special characters in dataset name and run name (#1762) 2024-04-18 22:49:10 +00:00
Marc Klingen fe6d8a38ec chore: release v2.30.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-18 18:25:26 +02:00
Marc KlingenandGitHub d4fecbb2a6 fix: support special characters for prompt names (#1763) 2024-04-18 14:20:20 +00:00
Marc KlingenandGitHub d92ba1d26b feat(ui): concise IOTableCell for small rowHeight across tables (#1766) 2024-04-18 13:42:48 +00:00
Marc KlingenandGitHub 61c2312f12 chore: add dev env flag (#1765) 2024-04-18 15:29:41 +02:00
Marc KlingenandGitHub 862a1395d5 feat(ui): render errors on sign in page and log unknown to sentry (#1767) 2024-04-18 14:56:14 +02:00
Marc KlingenandGitHub 21e9e17e93 docs: add comment on too large page sizes to openapi reference (#1772) 2024-04-18 11:17:11 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5b32d4b9a2 chore(deps): bump react-responsive from 9.0.2 to 10.0.0 (#1749)
Bumps [react-responsive](https://github.com/yocontra/react-responsive) from 9.0.2 to 10.0.0.
- [Changelog](https://github.com/yocontra/react-responsive/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yocontra/react-responsive/compare/v9.0.2...v10.0.0)

---
updated-dependencies:
- dependency-name: react-responsive
  dependency-type: direct:production
  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-17 23:17:07 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ea5617d621 chore(deps-dev): bump @typescript-eslint/parser from 7.6.0 to 7.7.0 (#1750)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.6.0 to 7.7.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.7.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  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-17 23:10:35 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9c72fddc20 chore(deps-dev): bump vitest from 1.4.0 to 1.5.0 (#1751)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.5.0/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-type: direct:development
  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-17 18:44:22 +00:00
Marc Klingen 548669e546 chore: release v2.29.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-17 17:08:09 +02:00
Marc Klingen a7a5870508 fix: do not load NODE_ENV for posthog in frontend 2024-04-17 16:58:55 +02:00
Marc Klingen 0385a52e2c chore: release v2.29.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-17 15:09:29 +02:00
Marc Klingen dd3f4824e3 fix: import types from correct package to fix runtime issue 2024-04-17 15:09:15 +02:00
Marc Klingen aa6145e341 chore: release v2.29.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-17 14:24:13 +02:00
Marc KlingenandGitHub a70f1d58a5 feat(datasets): add metadata to dataset and datasetItems (#1756) 2024-04-17 14:22:20 +02:00
Max DeichmannandGitHub 16870fb489 fix: use trash icon for eval config (#1757) 2024-04-17 11:15:19 +00:00
Max DeichmannandGitHub 24b8722ad5 fix: disable eval config after confirmation only (#1746) 2024-04-16 22:08:14 +00:00
d0cf2f56a0 feat(scores): Add a score value filter to api (#1745)
---------

Co-authored-by: DHacky <dhgacky@gmail.com>
2024-04-16 21:46:52 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
20e3c25fd6 chore(deps): bump the patches group with 6 updates (#1741)
Bumps the patches group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@headlessui/react](https://github.com/tailwindlabs/headlessui/tree/HEAD/packages/@headlessui-react) | `1.7.18` | `1.7.19` |
| [@langchain/core](https://github.com/langchain-ai/langchainjs) | `0.1.57` | `0.1.58` |
| [ai](https://github.com/vercel/ai) | `3.0.22` | `3.0.23` |
| [react-day-picker](https://github.com/gpbl/react-day-picker) | `8.10.0` | `8.10.1` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.2.78` | `18.2.79` |
| [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) | `0.5.13` | `0.5.14` |


Updates `@headlessui/react` from 1.7.18 to 1.7.19
- [Release notes](https://github.com/tailwindlabs/headlessui/releases)
- [Changelog](https://github.com/tailwindlabs/headlessui/blob/main/packages/@headlessui-react/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/headlessui/commits/@headlessui/react@v1.7.19/packages/@headlessui-react)

Updates `@langchain/core` from 0.1.57 to 0.1.58
- [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 `ai` from 3.0.22 to 3.0.23
- [Release notes](https://github.com/vercel/ai/releases)
- [Commits](https://github.com/vercel/ai/compare/ai@3.0.22...ai@3.0.23)

Updates `react-day-picker` from 8.10.0 to 8.10.1
- [Release notes](https://github.com/gpbl/react-day-picker/releases)
- [Commits](https://github.com/gpbl/react-day-picker/compare/v8.10.0...v8.10.1)

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

Updates `prettier-plugin-tailwindcss` from 0.5.13 to 0.5.14
- [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.13...v0.5.14)

---
updated-dependencies:
- dependency-name: "@headlessui/react"
  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: ai
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patches
- dependency-name: react-day-picker
  dependency-type: direct:production
  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: prettier-plugin-tailwindcss
  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-16 19:58:15 +00:00
b2be82b436 feat: add posthog to chatCompletion endpoint (#1742)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-04-16 19:42:14 +00:00
Marc Klingen 779771950b chore: add crisp env to env.mjs 2024-04-16 21:36:38 +02:00
Max DeichmannandGitHub fdfc64b853 perf: add internal model index (#1743) 2024-04-16 19:54:31 +02:00
Marc Klingen ca43da7562 chore: release v2.28.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-16 18:07:41 +02:00
Marc KlingenandGitHub 12a2459a1d feat(api): add fromTimestamp and toTimestamp to GET metrics/daily (#1740) 2024-04-16 16:06:27 +00:00
Marc KlingenandGitHub 5c1a7535ee feat: sentry exception tracking for authentication error page (#1739) 2024-04-16 17:26:54 +02:00
Marc Klingen 32f4e5c64f fix(ui): "update promt" on submit of prompt form 2024-04-16 17:21:43 +02:00
Max DeichmannandGitHub 06b8331a07 fix: rename template select (#1738) 2024-04-16 15:17:37 +00:00
Max DeichmannandGitHub 817554f1c9 feat: move evals to own main menu (#1734) 2024-04-16 15:11:20 +00:00
Marc KlingenandGitHub 37d982a32f perf: add additional db index on models.name (#1729) 2024-04-16 17:02:21 +02:00
Max DeichmannandGitHub 1f75563d55 chore: fix vite version (#1737) 2024-04-16 14:59:39 +00:00
Max DeichmannandGitHub f5775500c8 fix: pin pnpm in docker image (#1735) 2024-04-16 14:50:42 +00:00
Marc KlingenandGitHub 97e26dc8f3 chore: add web/src/ee (#1736) 2024-04-16 16:40:26 +02:00
Marc Klingen 1d733e39ff fix: add help tooltip to single prompt page 2024-04-16 15:11:58 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
12abeac07a chore(deps): bump the sentry group with 4 updates (#1724)
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.110.0 to 7.110.1
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/7.110.1/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.110.0...7.110.1)

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

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

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

---
updated-dependencies:
- dependency-name: "@sentry/nextjs"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: sentry
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: sentry
- dependency-name: "@sentry/profiling-node"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: sentry
- dependency-name: "@sentry/types"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-16 11:04:05 +00:00
Hassieb PakzadandGitHub 6478456e6c fix: adds error handling to chat completion calls (#1733) 2024-04-16 10:54:39 +00:00
Marc Klingen 5e27d36d74 chore: release v2.27.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-16 12:19:28 +02:00
Marc Klingen 8461a7a305 chore: add ADMIN_API_KEY to env example 2024-04-16 12:18:11 +02:00
Marc Klingen 5fd3cd218a chore: remove console log 2024-04-16 12:16:28 +02:00
Marc KlingenandGitHub 38cc982924 feat(ee): domain-specific sso providers (#1713) 2024-04-16 10:08:41 +00:00
Max Deichmann 3d81ef54af chore: release v2.26.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (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-16 11:39:21 +02:00
Max DeichmannandGitHub 08d19a6d11 perf: do not send empty events to worker (#1732) 2024-04-16 09:34:58 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
817ab669a8 chore(deps-dev): bump @testing-library/react from 14.2.2 to 15.0.2 (#1726)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 14.2.2 to 15.0.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.2...v15.0.2)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  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-16 08:57:50 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1294ed9b9b chore(deps): bump posthog-js from 1.116.2 to 1.122.0 (#1727)
Bumps [posthog-js](https://github.com/PostHog/posthog-js) from 1.116.2 to 1.122.0.
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/compare/v1.116.2...v1.122.0)

---
updated-dependencies:
- dependency-name: posthog-js
  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-16 08:51:04 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d3d236d63e chore(deps-dev): bump release-it from 17.1.1 to 17.2.0 (#1728)
Bumps [release-it](https://github.com/release-it/release-it) from 17.1.1 to 17.2.0.
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/main/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/17.1.1...17.2.0)

---
updated-dependencies:
- dependency-name: release-it
  dependency-type: direct:development
  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-16 08:44:22 +00:00
312 changed files with 12164 additions and 4209 deletions
+6 -1
View File
@@ -14,6 +14,9 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="secret"
# Langfuse Cloud Environment
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION="DEV"
# Langfuse experimental features
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES="true"
@@ -43,4 +46,6 @@ REDIS_HOST="127.0.0.1"
REDIS_PORT=6379
REDIS_AUTH="myredissecret"
LANGFUSE_WORKER_PASSWORD=mybasicauthsecret
LANGFUSE_WORKER_PASSWORD=mybasicauthsecret
# openssl rand -base64 32 used only here
ENCRYPTION_KEY=6c16874e5c0f0cc74ddec00425fa99fbe9ffbe412b7d5d906a4b00005df91403
+23
View File
@@ -76,6 +76,23 @@ LANGFUSE_CSP_ENFORCE_HTTPS="true"
# The page size can be adjusted if needed to optimize performance
# DB_EXPORT_PAGE_SIZE=1000
# Disable logging to `events` table in db
# ENABLE_EVENT_LOG=false
### START Envs to be deprecated in Langfuse v3.0
# Disable the expensive analytics queries and related features
# LANGFUSE_DISABLE_EXPENSIVE_POSTGRES_QUERIES="true"
# Customize the default datetime offset filter used in for the UI tables
# LANGFUSE_DEFAULT_TABLE_DATETIME_OFFSET="7" # int (days)
### END Envs to be deprecated in Langfuse v3.0
### START Langfuse Cloud Config
# Used for Langfuse Cloud deployments
@@ -114,4 +131,10 @@ LANGFUSE_CSP_ENFORCE_HTTPS="true"
# Crisp chat
# NEXT_PUBLIC_CRISP_WEBSITE_ID=
# Encryption
# ENCRYPTION_KEY=
# Admin API
# ADMIN_API_KEY=
### END Langfuse Cloud Config
+15 -1
View File
@@ -12,10 +12,24 @@ body:
- type: textarea
attributes:
label: To reproduce
description: Describe how to reproduce your bug. Steps, code snippets, reproduction repos etc.
description: Describe how to reproduce your bug. Please provide detailed steps, code snippets, reproduction repos etc.
validations:
required: true
- type: textarea
attributes:
label: SDK and container versions
description: If you're experiencing an issue with an integration or SDK, please ensure you're using the latest version. If you're self-hosting Langfuse, check that you're running the most recent version. If updating isn't an option, please provide the specific versions you're currently using.
- type: textarea
attributes:
label: Additional information
description: Add any other information related to the bug here, screenshots if applicable.
- type: dropdown
id: contribute
attributes:
label: Are you interested to contribute a fix for this bug?
description: If this is a confirmed bug, the maintainers are happy to support with guidance and review.
options:
- "No"
- "Yes"
validations:
required: true
+6 -5
View File
@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v3
with:
version: 8
version: 8.15.5
- uses: actions/setup-node@v3
with:
node-version: 20
@@ -68,7 +68,7 @@ jobs:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v3
with:
version: 8
version: 8.15.5
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
@@ -111,6 +111,7 @@ jobs:
node-version: [20]
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
steps:
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
@@ -120,7 +121,7 @@ jobs:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v3
with:
version: 8
version: 8.15.5
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
@@ -159,7 +160,7 @@ jobs:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v3
with:
version: 8
version: 8.15.5
- uses: actions/setup-node@v3
with:
node-version: 20
@@ -221,7 +222,7 @@ jobs:
steps:
- uses: pnpm/action-setup@v3
with:
version: 8
version: 8.15.5
- name: Setup node
uses: actions/setup-node@v3
with:
+75 -45
View File
@@ -53,53 +53,63 @@ A good first step is to search for open [issues](https://github.com/langfuse/lan
### Architecture Overview
> [!NOTE]
> Infrastructure will change in Langfuse version 3.0. More in the [GitHub Discussions](https://github.com/orgs/langfuse/discussions/1902).
> `langfuse/langfuse/worker` is under active development and not recommended for production use in Langfuse 2.x
```mermaid
flowchart TB
subgraph s4["Clients"]
subgraph s2["langfuse/langfuse-python"]
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
end
subgraph s3["langfuse/langfuse-js"]
JS["JS SDK"]
LCJS["Langchain JS Integration"] -->|extends| JS
Flowise -->|uses| LCJS
end
end
subgraph s4["Clients"]
subgraph s2["langfuse/langfuse-python"]
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
end
subgraph s3["langfuse/langfuse-js"]
JS["JS SDK"]
LCJS["Langchain JS Integration"] -->|extends| JS
Flowise -->|uses| LCJS
end
end
DB[Postgres Database]
subgraph s1["Application (langfuse/langfuse)"]
API[Public HTTP API]
G[TRPC API]
I[NextAuth]
H[React Frontend]
Prisma[Prisma ORM]
H --> G
H --> I
G --> I
G --- Prisma
API --- Prisma
I --- Prisma
end
Prisma --- DB
JS --- API
Python --- API
DB[Postgres Database]
Redis[Redis]
subgraph s1["Application (langfuse/langfuse/web)"]
API[Public HTTP API]
G[TRPC API]
I[NextAuth]
H[React Frontend]
Prisma[Prisma ORM]
H --> G
H --> I
G --> I
G --- Prisma
API --- Prisma
I --- Prisma
end
subgraph s5["Application (langfuse/langfuse/worker)"]
Worker_API[Public HTTP API]
end
API --> Worker_API
Worker_API --- DB
Worker_API --- Redis
Prisma --- DB
JS --- API
Python --- API
```
### Database Overview
### Network Overview
The diagram below may not show all relationships if the foreign key is not defined in the database schema. For instance, `trace_id` in the `observation` table is not defined as a foreign key to the `trace` table to allow unordered ingestion of these objects, but it is still a foreign key in the application code.
Full database schema: [packages/shared/prisma/schema.prisma](packages/shared/prisma/schema.prisma)
<img src="./packages/shared/prisma/database.svg">
### Infrastructure & Network Overview
> [!NOTE]
> This will change in Langfuse version 3.0. More in the [GitHub Discussions](https://github.com/orgs/langfuse/discussions/1902).
```mermaid
flowchart LR
@@ -114,15 +124,25 @@ flowchart LR
App --- DB
```
### Database Overview
The diagram below may not show all relationships if the foreign key is not defined in the database schema. For instance, `trace_id` in the `observation` table is not defined as a foreign key to the `trace` table to allow unordered ingestion of these objects, but it is still a foreign key in the application code.
Full database schema: [packages/shared/prisma/schema.prisma](packages/shared/prisma/schema.prisma)
<img src="./packages/shared/prisma/database.svg">
## Repository Structure
We built a monorepo using [pnpm](https://pnpm.io/motivation) and [turbo](https://turbo.build/repo/docs) to manage the dependencies and build process. The monorepo contains the following packages:
- `web`: is the main application package providing Frontend and Backend APIs for Langfuse.
- `worker` (no production yet): contains an application for asynchronous processing of tasks. This package is not yet used in production.
- `shared`: contains shared code between the above packages.
- `config-eslint`: contains eslint configurations which are shared between the above packages.
- `config-typescript`: contains typescript configurations which are shared between the above packages.
- `packages`:
- `shared`: contains shared code between the above packages.
- `config-eslint`: contains eslint configurations which are shared between the above packages.
- `config-typescript`: contains typescript configurations which are shared between the above packages.
- `ee`: contains all enterprise features. See [EE README](ee/README.md) for more details.
## Development Setup
@@ -133,7 +153,7 @@ Requirements
**Steps**
1. Fork the the repository and clone it locally
1. Fork the repository and clone it locally
2. Run the development database
```bash
@@ -270,6 +290,16 @@ When a new release is tagged on the `main` branch (excluding prereleases), it tr
1. The Docker image is published to GitHub Packages with the version number and `latest` tag.
2. The deployment is carried out on Langfuse Cloud. This is done by force pushing the `main` branch to the `production` branch during every release, using the [`release.yml`](.github/workflows/release.yml) GitHub Action.
## Using secrets stored in 1Password
When applying changes to non-local environments, you may need to use secrets stored in 1Password. We use the 1Password CLI for this purpose.
Example:
```bash
op run --env-file="./.env" -- pnpm --filter=shared run db:deploy
```
## License
Langfuse is MIT licensed, except for `ee/` folder. See [LICENSE](LICENSE) and [docs](https://langfuse.com/docs/open-source) for more details.
+5 -5
View File
@@ -1,10 +1,10 @@
Copyright (c) 2023 Finto Technologies GmbH
Copyright (c) 2023--2024 Langfuse GmbH
Portions of this software are licensed as follows:
* All content that resides under the "ee/" directory of this repository, if that directory exists, is licensed under the license defined in "ee/LICENSE".
* All third party components incorporated into the Finto Technologies Software are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "MIT Expat" license as defined below.
- All content that resides under the "ee/" and/or "web/src/ee" directories of this repository, if these directories exist, is licensed under the license defined in "ee/LICENSE".
- All third party components incorporated into the Finto Technologies Software are licensed under the original license provided by the owner of the applicable component.
- Content outside of the above mentioned directories or restrictions above is available under the "MIT Expat" license as defined below.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -22,4 +22,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
+16 -11
View File
@@ -26,7 +26,7 @@
<br/>
<br/>
<div>
<img src="https://img.shields.io/badge/License-MIT-red.svg?style=flat-square" alt="MIT License">
<a href="https://github.com/langfuse/langfuse/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-red.svg?style=flat-square" alt="MIT License"></a>
<a href="https://www.ycombinator.com/companies/langfuse"><img src="https://img.shields.io/badge/Y%20Combinator-W23-orange?style=flat-square" alt="Y Combinator W23"></a>
<a href="https://github.com/langfuse/langfuse/pkgs/container/langfuse"><img alt="Docker Image" src="https://img.shields.io/badge/docker-langfuse-blue?logo=Docker&logoColor=white&style=flat-square"></a>
<a href="https://www.npmjs.com/package/langfuse"><img src="https://img.shields.io/npm/v/langfuse?style=flat-square&label=npm+langfuse" alt="langfuse npm package"></a>
@@ -37,17 +37,22 @@
## Overview
_Unmute video for voice-over_
https://github.com/langfuse/langfuse/assets/2834609/a94062e9-c782-4ee9-af59-dee6370149a8
### Develop
- **Observability:** Instrument your app and start ingesting traces to Langfuse ([Quickstart](https://langfuse.com/docs/get-started), [Integrations](https://langfuse.com/docs/integrations) [Tracing](https://langfuse.com/docs/tracing))
- **Langfuse UI:** Inspect and debug complex logs ([Demo](https://langfuse.com/docs/demo), [Tracing](https://langfuse.com/docs/tracing))
- **Prompts:** Manage, version and deploy prompts from within Langfuse ([Prompt Management](https://langfuse.com/docs/prompts))
- **Prompt Management:** Manage, version and deploy prompts from within Langfuse ([Prompt Management](https://langfuse.com/docs/prompts/get-started))
- **Prompt Engineering:** Test and iterate on your prompts with the [LLM Playground](https://langfuse.com/docs/playground)
### Monitor
- **Analytics:** Track metrics (cost, latency, quality) and gain insights from dashboards & data exports ([Analytics](https://langfuse.com/docs/analytics))
- **Evals:** Collect and calculate scores for your LLM completions ([Scores & Evaluations](https://langfuse.com/docs/scores))
- Run model-based evaluations ([Model-based evaluations](https://langfuse.com/docs/scores/model-based-evals))
- Run model-based evaluations ([Model-based evaluations](https://langfuse.com/docs/scores/model-based-evals)) within Langfuse
- Collect user feedback ([User Feedback](https://langfuse.com/docs/scores/user-feedback))
- Manually score observations in Langfuse ([Manual Scores](https://langfuse.com/docs/scores/manually))
@@ -101,13 +106,13 @@ See the [→ Quickstart](https://langfuse.com/docs/get-started) to integrate Lan
### Integrations
| Integration | Supports | Description |
| ---------------------------------------------------------------- | ------------- | ---------------------------------------------------------------- |
| [**SDK** - _recommended_](https://langfuse.com/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI SDK](https://langfuse.com/docs/integrations/openai) | Python, JS/TS | Automated instrumentation of OpenAI SDK. |
| [Langchain](https://langfuse.com/docs/integrations/langchain) | Python, JS/TS | Instrumentation via Langchain callbacks. |
| [LlamaIndex](https://langfuse.com/docs/integrations/llama-index) | Python | Automated instrumentation via LlamaIndex callback system. |
| [API](https://langfuse.com/docs/api) | | Directly call the public API. OpenAPI spec available. |
| Integration | Supports | Description |
| ---------------------------------------------------------------- | ------------- | ----------------------------------------------------------- |
| [**SDK** - _recommended_](https://langfuse.com/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI SDK](https://langfuse.com/docs/integrations/openai) | Python, JS/TS | Automated instrumentation of OpenAI SDK. |
| [Langchain](https://langfuse.com/docs/integrations/langchain) | Python, JS/TS | Instrumentation via Langchain callbacks. |
| [LlamaIndex](https://langfuse.com/docs/integrations/llama-index) | Python | Automated instrumentation via LlamaIndex callback system. |
| [API](https://langfuse.com/docs/api) | | Directly call the public API. OpenAPI spec available. |
External projects/packages that integrate with Langfuse:
@@ -142,7 +147,7 @@ In order of preference the best way to communicate with us:
## License
This repository is MIT licensed, except for the `ee/` folder. See [LICENSE](LICENSE) and [docs](https://langfuse.com/docs/open-source) for more details.
This repository is MIT licensed, except for the `ee` folders. See [LICENSE](LICENSE) and [docs](https://langfuse.com/docs/open-source) for more details.
## Misc
+8
View File
@@ -0,0 +1,8 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
extends: ["@repo/eslint-config/library.js"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
};
+37
View File
@@ -0,0 +1,37 @@
Langfuse Enterprise License (the “Enterprise License”)
Copyright (c) 2024 Langfuse GmbH ('Langfuse')
With regard to the Langfuse Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Langfuse Terms of Service, available
at https://langfuse.com/terms (the “Enterprise Terms”), or other
agreement governing the use of the Software, as agreed by you and Langfuse,
and otherwise have a valid Langfuse Enterprise license.
Subject to the foregoing sentence, you are free to
modify this Software and publish patches to the Software. You agree that Langfuse
and/or its licensors (as applicable) retain all right, title and interest in and
to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Langfuse Enterprise license.
Notwithstanding the foregoing, you may copy and modify
the Software for development and testing purposes, without requiring a
subscription. You agree that Langfuse and/or its licensors (as applicable) retain
all right, title and interest in and to all such modifications. You are not
granted any other rights beyond what is expressly stated herein. Subject to the
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Langfuse Software, those
components are licensed under the original license provided by the owner of the
applicable component.
+3 -1
View File
@@ -1,3 +1,5 @@
# Enterprise Edition
While we are currently working 100% on the open source version, we consider financing the project by developing some premium features as part of an enterprise version under a commercial license. Please reach out to us if you have specific requirements: enterprise@langfuse.com
This folder includes features that are only available in the Enterprise Edition of Langfuse and on Langfuse Cloud.
See [LICENSE](../LICENSE) and [docs](https://langfuse.com/docs/open-source) for more details.
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@langfuse/ee",
"version": "1.0.0",
"private": true,
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"exports": {
".": {
"import": "./dist/src/index.js",
"require": "./dist/src/index.js"
},
"./sso": {
"import": "./dist/src/sso/index.js",
"require": "./dist/src/sso/index.js"
}
},
"engines": {
"node": ">=20"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix"
},
"dependencies": {
"@langfuse/shared": "workspace:*",
"next": "^14.2.3",
"next-auth": "^4.24.7",
"zod": "^3.22.4"
},
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"@types/node": "^20.11.29",
"@typescript-eslint/parser": "^7.7.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"tsc-watch": "^6.2.0",
"typescript": "^5.4.5"
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"trailingComma": "es5",
"printWidth": 120
}
+4
View File
@@ -0,0 +1,4 @@
import { env } from "../env";
export const isEeAvailable: boolean =
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined;
+9
View File
@@ -0,0 +1,9 @@
import { z } from "zod";
import { env as sharedEnv } from "@langfuse/shared";
const EnvSchema = z.object({
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION: z.string().optional(),
ADMIN_API_KEY: z.string().optional(),
});
export const env = { ...sharedEnv, ...EnvSchema.parse(process.env) };
+1
View File
@@ -0,0 +1 @@
export { isEeAvailable } from "./ee-license-check";
+236
View File
@@ -0,0 +1,236 @@
import { type Provider } from "next-auth/providers/index";
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 { isEeAvailable } from "..";
import { prisma } from "@langfuse/shared/src/db";
import { encrypt, decrypt } from "@langfuse/shared/encryption";
import { SsoProviderSchema } from "./types";
import { type NextApiRequest, type NextApiResponse } from "next";
import { env } from "../env";
// Local cache for SSO configurations
let cachedSsoConfigs: {
data: SsoProviderSchema[];
timestamp: number;
} | null = null;
/**
* Get all SSO configurations from the database or from local cache and parse them into SsoProviderSchema objects.
*
* @returns {Promise<SsoProviderSchema[]>} - A list of all SSO configurations. Empty array if none are configured or EE is not available.
*/
async function getSsoConfigs(): Promise<SsoProviderSchema[]> {
if (!isEeAvailable) return [];
const CACHE_TTL = 60 * 1000; // 1 minute
// Set/refresh the cache if it's empty or expired
if (
cachedSsoConfigs === null ||
Date.now() - cachedSsoConfigs.timestamp > CACHE_TTL
) {
// transform into zod object
const dbConfigs = await prisma.ssoConfig.findMany();
const parsedSsoConfigs = dbConfigs
.map((v) => {
try {
const parsedValue = SsoProviderSchema.parse(v);
return parsedValue;
} catch (e) {
console.error(
`Failed to parse SSO provider config for domain ${v.domain}`,
e
);
return null;
}
})
.filter((parsed) => parsed !== null) as SsoProviderSchema[];
cachedSsoConfigs = {
data: parsedSsoConfigs,
timestamp: Date.now(),
};
}
return cachedSsoConfigs.data;
}
/**
* Load all custom SSO providers from the database. To be used within `providers` in NextAuth backend configuration.
*
* @returns {Promise<Provider[]>} - A list of all custom SSO providers.
*/
export async function loadSsoProviders(): Promise<Provider[]> {
if (!isEeAvailable) return [];
const ssoConfigs = await getSsoConfigs();
const providers: Provider[] = [];
for (const dbSsoConfig of ssoConfigs) {
const provider = dbToNextAuthProvider(dbSsoConfig);
if (provider !== null) providers.push(provider);
}
return providers;
}
/**
* @returns `true` if any custom SSO provider is configured in the database.
*/
export async function isAnySsoConfigured(): Promise<boolean> {
if (!isEeAvailable) return false;
const ssoConfigs = await getSsoConfigs();
return ssoConfigs.length > 0;
}
/**
* Get the custom SSO providerId for a domain. To be used with NextAuth's `signIn(providerId)`.
*
* @param domain - The domain to check for a custom SSO provider, e.g. "example.com".
* @returns `providerId` or null if none is configured or EE is not available.
*/
export async function getSsoAuthProviderIdForDomain(
domain: string
): Promise<string | null> {
if (!isEeAvailable) return null;
const ssoConfig = (await getSsoConfigs()).find(
(ssoConfig) => ssoConfig.domain === domain.toLowerCase()
);
if (!ssoConfig) return null;
return getAuthProviderIdForSsoConfig(ssoConfig);
}
/**
* Converts a SsoProviderConfig to a NextAuth Provider instance.
*
* @param {SsoProviderSchema} provider - The SSO configuration from the database.
* @returns {Provider | null} - A NextAuth Provider instance or null if parsing fails or no custom credentials are used for this SSO config.
*/
const dbToNextAuthProvider = (provider: SsoProviderSchema): Provider | null => {
// If the SsoConfig does not use custom credentials, return null as no additional provider needs to be added to NextAuth
if (!provider.authConfig) return null;
if (provider.authProvider === "google")
return GoogleProvider({
id: getAuthProviderIdForSsoConfig(provider), // use the domain as the provider id as we use domain-specific credentials
...provider.authConfig,
clientSecret: decrypt(provider.authConfig.clientSecret),
});
else if (provider.authProvider === "github")
return GitHubProvider({
id: getAuthProviderIdForSsoConfig(provider), // use the domain as the provider id as we use domain-specific credentials
...provider.authConfig,
clientSecret: decrypt(provider.authConfig.clientSecret),
});
else if (provider.authProvider === "auth0")
return Auth0Provider({
id: getAuthProviderIdForSsoConfig(provider), // use the domain as the provider id as we use domain-specific credentials
...provider.authConfig,
clientSecret: decrypt(provider.authConfig.clientSecret),
});
else if (provider.authProvider === "okta")
return OktaProvider({
id: getAuthProviderIdForSsoConfig(provider), // use the domain as the provider id as we use domain-specific credentials
...provider.authConfig,
clientSecret: decrypt(provider.authConfig.clientSecret),
});
else if (provider.authProvider === "azure-ad")
return AzureADProvider({
id: getAuthProviderIdForSsoConfig(provider), // use the domain as the provider id as we use domain-specific credentials
...provider.authConfig,
clientSecret: decrypt(provider.authConfig.clientSecret),
});
else {
// Type check to ensure we handle all providers
// eslint-disable-next-line no-unused-vars
const _: never = provider;
throw new Error(
`Unrecognized SSO provider for domain ${(provider as any).domain}`
);
}
};
/**
* Get the custom SSO providerId for a database SSO configuration. To be used with NextAuth's `signIn(providerId)`.
*
* @param {DbSsoConfig} dbSsoConfig - The SSO configuration from the database.
* @returns {string} - The providerId used in NextAuth.
*/
const getAuthProviderIdForSsoConfig = (
dbSsoConfig: SsoProviderSchema
): string => {
if (!dbSsoConfig.authConfig) return dbSsoConfig.authProvider;
return `${dbSsoConfig.domain}.${dbSsoConfig.authProvider}`;
};
export async function createNewSsoConfigHandler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
if (!isEeAvailable) {
res.status(403).json({ error: "EE is not available" });
return;
}
// allow only POST requests
if (req.method !== "POST") {
res.status(405).json({ error: "Method Not Allowed" });
return;
}
// check if ADMIN_API_KEY is set
if (!env.ADMIN_API_KEY) {
res.status(500).json({ error: "ADMIN_API_KEY is not set" });
return;
}
if (!env.ENCRYPTION_KEY) {
res.status(500).json({ error: "ENCRYPTION_KEY is not set" });
return;
}
// check bearer token
const { authorization } = req.headers;
if (!authorization) {
res
.status(401)
.json({ error: "Unauthorized: No authorization header provided" });
return;
}
const [scheme, token] = authorization.split(" ");
if (scheme !== "Bearer" || !token || token !== env.ADMIN_API_KEY) {
res.status(401).json({ error: "Unauthorized: Invalid token" });
return;
}
const body = SsoProviderSchema.safeParse(req.body);
if (!body.success) {
res.status(400).json({ error: body.error });
return;
}
const { domain, authProvider, authConfig } = body.data;
const encryptedClientSecret = authConfig
? {
...authConfig,
clientSecret: encrypt(authConfig.clientSecret),
}
: undefined;
await prisma.ssoConfig.create({
data: {
domain,
authProvider,
authConfig: encryptedClientSecret,
},
});
res.status(201).json({
message: "SSO configuration created successfully",
});
} catch (e) {
console.log(e);
res.status(500).json({ error: "Internal Server Error" });
}
}
+81
View File
@@ -0,0 +1,81 @@
import { z } from "zod";
const base = z.object({
domain: z.string().refine((v) => v === v.toLowerCase(), {
message: "Domain must be lowercase",
}),
});
export const GoogleProviderSchema = base.extend({
authProvider: z.literal("google"),
authConfig: z
.object({
clientId: z.string(),
clientSecret: z.string(),
allowDangerousEmailAccountLinking: z.boolean().optional().default(false),
})
.nullish(),
});
export const GithubProviderSchema = base.extend({
authProvider: z.literal("github"),
authConfig: z
.object({
clientId: z.string(),
clientSecret: z.string(),
allowDangerousEmailAccountLinking: z.boolean().optional().default(false),
})
.nullish(),
});
export const Auth0ProviderSchema = base.extend({
authProvider: z.literal("auth0"),
authConfig: z
.object({
clientId: z.string(),
clientSecret: z.string(),
issuer: z.string(),
allowDangerousEmailAccountLinking: z.boolean().optional().default(false),
})
.nullish(),
});
export const OktaProviderSchema = base.extend({
authProvider: z.literal("okta"),
authConfig: z
.object({
clientId: z.string(),
clientSecret: z.string(),
issuer: z.string(),
allowDangerousEmailAccountLinking: z.boolean().optional().default(false),
})
.nullish(),
});
export const AzureAdProviderSchema = base.extend({
authProvider: z.literal("azure-ad"),
authConfig: z
.object({
clientId: z.string(),
clientSecret: z.string(),
tenantId: z.string(),
allowDangerousEmailAccountLinking: z.boolean().optional().default(false),
})
.nullish(),
});
export type GoogleProviderSchema = z.infer<typeof GoogleProviderSchema>;
export type GithubProviderSchema = z.infer<typeof GithubProviderSchema>;
export type Auth0ProviderSchema = z.infer<typeof Auth0ProviderSchema>;
export type OktaProviderSchema = z.infer<typeof OktaProviderSchema>;
export type AzureAdProviderSchema = z.infer<typeof AzureAdProviderSchema>;
export const SsoProviderSchema = z.discriminatedUnion("authProvider", [
GoogleProviderSchema,
GithubProviderSchema,
Auth0ProviderSchema,
OktaProviderSchema,
AzureAdProviderSchema,
]);
export type SsoProviderSchema = z.infer<typeof SsoProviderSchema>;
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"moduleResolution": "NodeNext",
"module": "NodeNext",
"lib": ["ES2020"],
"outDir": "./dist",
"types": ["node"],
"target": "ES2020",
"rootDir": "."
},
"include": ["."],
"exclude": ["node_modules", "dist"]
}
+1 -1
View File
@@ -15,7 +15,7 @@ groups:
version: 0.0.26
output:
location: local-file-system
path: ../../../web/generated/openapi-client
path: ../../../web/public/generated/api-client
config:
namespaceExport: Langfuse
allowCustomFetcher: true
+7 -1
View File
@@ -1,11 +1,17 @@
name: langfuse
docs: |
## Authentication
Authenticate with the API using Basic Auth, get API keys in the project settings:
Authenticate with the API using [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication), get API keys in the project settings:
- username: Langfuse Public Key
- password: Langfuse Secret Key
## Exports
- OpenAPI spec: https://cloud.langfuse.com/generated/api/openapi.yml
- Postman collection: https://cloud.langfuse.com/generated/postman/collection.json
error-discrimination:
strategy: status-code
auth: basic
+3
View File
@@ -127,6 +127,7 @@ types:
id: string
name: string
description: optional<string>
metadata: optional<unknown>
projectId: string
createdAt: datetime
updatedAt: datetime
@@ -151,6 +152,7 @@ types:
status: DatasetStatus
input: optional<unknown>
expectedOutput: optional<unknown>
metadata: optional<unknown>
sourceTraceId: optional<string>
sourceObservationId: optional<string>
datasetId: string
@@ -209,6 +211,7 @@ types:
enum:
- API
- REVIEW
- EVAL
errors:
Error:
@@ -26,6 +26,9 @@ types:
datasetName: string
input: optional<unknown>
expectedOutput: optional<unknown>
metadata: optional<unknown>
sourceTraceId: optional<string>
sourceObservationId: optional<string>
id:
type: optional<string>
docs: Dataset items are upserted on their id
+1
View File
@@ -47,3 +47,4 @@ types:
properties:
name: string
description: optional<string>
metadata: optional<unknown>
+8 -1
View File
@@ -29,6 +29,12 @@ service:
type: optional<string>
allow-multiple: true
docs: Optional filter for metrics where traces include all of these tags
fromTimestamp:
type: optional<datetime>
docs: Optional filter to only include traces on or after a certain datetime (ISO 8601)
toTimestamp:
type: optional<datetime>
docs: Optional filter to only include traces before a certain datetime (ISO 8601)
response: DailyMetrics
types:
DailyMetrics:
@@ -41,12 +47,13 @@ types:
properties:
date: date
countTraces: integer
countObservations: integer
totalCost: double
usage: list<UsageByModel>
UsageByModel:
docs: Daily usage of a given model. Usage corresponds to the unit set for the specific model (e.g. tokens).
properties:
model: string
model: optional<string>
inputUsage: integer
outputUsage: integer
totalUsage: integer
+3 -3
View File
@@ -24,10 +24,10 @@ service:
query-parameters:
page:
type: optional<integer>
docs: page number, starts at 1
docs: Page number, starts at 1.
limit:
type: optional<integer>
docs: limit of items per page
docs: Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit.
name: optional<string>
userId: optional<string>
type: optional<string>
@@ -35,7 +35,7 @@ service:
parentObservationId: optional<string>
fromStartTime:
type: optional<datetime>
docs: Retrieve only observations with a start_time greater than this timestamp.
docs: Retrieve only observations with a start_time greater than this datetime (ISO 8601).
response: ObservationsViews
types:
+44 -11
View File
@@ -4,18 +4,42 @@ imports:
pagination: ./utils/pagination.yml
service:
auth: true
base-path: /api/public
base-path: /api/public/v2
endpoints:
get:
docs: Get a prompt
method: GET
path: /prompts/{promptName}
path-parameters:
promptName:
type: string
docs: The name of the prompt
request:
name: GetPromptRequest
query-parameters:
version:
type: optional<integer>
docs: Version of the prompt to be retrieved.
label:
type: optional<string>
docs: Label of the prompt to be retrieved. Defaults to "production" if no label or version is set.
response: Prompt
list:
docs: Get a list of prompt names with versions and labels
method: GET
path: /prompts
request:
name: GetParameterRequest
name: ListPromptsMetaRequest
query-parameters:
name: string
version: optional<integer>
response: Prompt
name: optional<string>
label: optional<string>
tag: optional<string>
page: optional<integer>
limit: optional<integer>
response: PromptMetaListResponse
create:
docs: Create a prompt
method: POST
@@ -24,6 +48,18 @@ service:
response: Prompt
types:
PromptMetaListResponse:
properties:
data: list<PromptMeta>
meta: pagination.MetaResponse
PromptMeta:
properties:
name: string
versions: list<integer>
labels: list<string>
tags: list<string>
CreatePromptRequest:
union:
chat: CreateChatPromptRequest
@@ -32,20 +68,16 @@ types:
CreateChatPromptRequest:
properties:
name: string
isActive:
docs: Should the prompt be promoted to production immediately?
type: boolean
prompt: list<ChatMessage>
config: optional<unknown>
labels: optional<list<string>>
CreateTextPromptRequest:
properties:
name: string
isActive:
docs: Should the prompt be promoted to production immediately?
type: boolean
prompt: string
config: optional<unknown>
labels: optional<list<string>>
Prompt:
union:
@@ -57,6 +89,7 @@ types:
name: string
version: integer
config: unknown
labels: list<string>
ChatMessage:
properties:
+12 -3
View File
@@ -21,15 +21,24 @@ service:
query-parameters:
page:
type: optional<integer>
docs: page number, starts at 1
docs: Page number, starts at 1.
limit:
type: optional<integer>
docs: limit of items per page
docs: Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit.
userId: optional<string>
name: optional<string>
fromTimestamp:
type: optional<datetime>
docs: Retrieve only scores newer than this timestamp.
docs: Retrieve only scores newer than this datetime (ISO 8601).
source:
type: commons.ScoreSource
docs: Retrieve only scores from a specific source.
operator:
type: optional<string>
docs: Retrieve only scores with <operator> value.
value:
type: optional<double>
docs: Retrieve only scores with <operator> value.
response: Scores
get-by-id:
docs: Get a score
+4 -4
View File
@@ -16,7 +16,7 @@ service:
docs: The unique langfuse identifier of a trace
response: commons.TraceWithFullDetails
list:
docs: Get list of traces
docs: Get list of traces.
method: GET
path: /traces
request:
@@ -24,15 +24,15 @@ service:
query-parameters:
page:
type: optional<integer>
docs: page number, starts at 1
docs: Page number, starts at 1
limit:
type: optional<integer>
docs: limit of items per page
docs: Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit.
userId: optional<string>
name: optional<string>
fromTimestamp:
type: optional<datetime>
docs: Retrieve only traces newer than this timestamp.
docs: Retrieve only traces newer than this datetime (ISO 8601).
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"
+2 -2
View File
@@ -6,7 +6,7 @@ groups:
version: 0.0.28
output:
location: local-file-system
path: ../../../web/generated/openapi-server
path: ../../../web/public/generated/api
config:
namespaceExport: Langfuse
allowCustomFetcher: true
@@ -33,7 +33,7 @@ groups:
version: 0.0.45
output:
location: local-file-system
path: ../../../generated/postman
path: ../../../web/public/generated/postman
# published:
# generators:
# - name: fernapi/fern-python-sdk
+1 -1
View File
@@ -1,4 +1,4 @@
{
"organization": "finto",
"version": "0.19.30"
"version": "0.21.0"
}
+5 -4
View File
@@ -1,11 +1,12 @@
{
"name": "langfuse",
"version": "2.26.0",
"version": "2.40.0",
"author": "engineering@langfuse.com",
"license": "MIT",
"private": true,
"engines": {
"node": ">=20"
"node": ">=20",
"pnpm": "<9"
},
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -29,8 +30,8 @@
"@release-it/bumper": "^6.0.1",
"dotenv-cli": "^7.4.1",
"prettier": "^3.2.5",
"release-it": "^17.1.1",
"turbo": "^1.13.2"
"release-it": "^17.2.1",
"turbo": "^1.13.3"
},
"release-it": {
"git": {
+3 -3
View File
@@ -9,11 +9,11 @@
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.6.0",
"@typescript-eslint/parser": "^7.7.0",
"@vercel/style-guide": "^6.0.0",
"eslint-config-next": "^14.2.1",
"eslint-config-next": "^14.2.3",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "^1.13.2",
"eslint-config-turbo": "^1.13.3",
"eslint-plugin-only-warn": "^1.1.0",
"typescript": "^5.4.5"
}
+12 -7
View File
@@ -20,6 +20,10 @@
"./src/server/auth": {
"import": "./dist/src/server/auth.js",
"require": "./dist/src/server/auth.js"
},
"./encryption": {
"import": "./dist/src/encryption/index.js",
"require": "./dist/src/encryption/index.js"
}
},
"scripts": {
@@ -28,6 +32,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"db:migrate": "DISABLE_ERD=false dotenv -e ../../.env -- npx prisma migrate dev",
"db:push": "DISABLE_ERD=false dotenv -e ../../.env -- npx prisma db push",
"db:reset": "dotenv -e ../../.env npx -- prisma migrate reset",
"db:deploy": "dotenv -e ../../.env npx -- prisma migrate deploy",
"db:seed": "dotenv -e ../../.env -- npx prisma db seed",
@@ -39,14 +44,14 @@
"seed": "ts-node -r tsconfig-paths/register -r dotenv/config --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
},
"dependencies": {
"@langchain/anthropic": "^0.1.14",
"@langchain/core": "^0.1.57",
"@langchain/anthropic": "^0.1.16",
"@langchain/core": "^0.1.61",
"@langchain/openai": "^0.0.28",
"@prisma/client": "^5.12.1",
"@prisma/client": "^5.13.0",
"@types/bcryptjs": "^2.4.6",
"bcryptjs": "^2.4.3",
"kysely": "^0.27.3",
"langchain": "^0.1.33",
"langchain": "^0.1.36",
"prisma-extension-kysely": "^2.1.0",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.5"
@@ -58,7 +63,7 @@
"@types/node": "^20.11.29",
"@types/pg": "^8.11.5",
"@types/uuid": "^9.0.8",
"@typescript-eslint/parser": "^7.6.0",
"@typescript-eslint/parser": "^7.7.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
@@ -66,12 +71,12 @@
"kysely-codegen": "^0.11.0",
"nodemon": "^3.0.3",
"prettier": "^3.2.5",
"prisma": "^5.12.1",
"prisma": "^5.13.0",
"prisma-erd-generator": "^1.11.2",
"prisma-kysely": "^1.8.0",
"ts-node": "^10.9.2",
"tsc-watch": "^6.2.0",
"typescript": "^5.4.5",
"vitest": "^1.3.1"
"vitest": "^1.5.3"
}
}
+43 -10
View File
@@ -4,13 +4,13 @@ export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
: ColumnType<T, T | undefined, T>;
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
export const MembershipRole = {
export const ProjectRole = {
OWNER: "OWNER",
ADMIN: "ADMIN",
MEMBER: "MEMBER",
VIEWER: "VIEWER"
} as const;
export type MembershipRole = (typeof MembershipRole)[keyof typeof MembershipRole];
export type ProjectRole = (typeof ProjectRole)[keyof typeof ProjectRole];
export const ObservationType = {
SPAN: "SPAN",
EVENT: "EVENT",
@@ -96,7 +96,7 @@ export type AuditLog = {
updated_at: Generated<Timestamp>;
user_id: string;
project_id: string;
user_project_role: MembershipRole;
user_project_role: ProjectRole;
resource_type: string;
resource_id: string;
action: string;
@@ -113,6 +113,7 @@ export type Dataset = {
id: string;
name: string;
description: string | null;
metadata: unknown | null;
project_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
@@ -122,6 +123,7 @@ export type DatasetItem = {
status: Generated<DatasetStatus>;
input: unknown | null;
expected_output: unknown | null;
metadata: unknown | null;
source_trace_id: string | null;
source_observation_id: string | null;
dataset_id: string;
@@ -197,17 +199,19 @@ export type JobExecution = {
job_input_trace_id: string | null;
job_output_score_id: string | null;
};
export type Membership = {
project_id: string;
user_id: string;
role: MembershipRole;
export type LlmApiKeys = {
id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
provider: string;
display_secret_key: string;
secret_key: string;
project_id: string;
};
export type MembershipInvitation = {
id: string;
email: string;
role: MembershipRole;
role: ProjectRole;
project_id: string;
sender_id: string | null;
created_at: Generated<Timestamp>;
@@ -290,6 +294,14 @@ export type ObservationView = {
calculated_total_cost: string | null;
latency: number | null;
};
export type PosthogIntegration = {
project_id: string;
encrypted_posthog_api_key: string;
posthog_host_name: string;
last_sync_at: Timestamp | null;
enabled: boolean;
created_at: Generated<Timestamp>;
};
export type Pricing = {
id: string;
model_name: string;
@@ -305,6 +317,13 @@ export type Project = {
name: string;
cloud_config: unknown | null;
};
export type ProjectMembership = {
project_id: string;
user_id: string;
role: ProjectRole;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
};
export type Prompt = {
id: string;
created_at: Generated<Timestamp>;
@@ -315,13 +334,15 @@ export type Prompt = {
name: string;
version: number;
type: Generated<string>;
is_active: boolean;
is_active: boolean | null;
config: Generated<unknown>;
tags: Generated<string[]>;
labels: Generated<string[]>;
};
export type Score = {
id: string;
timestamp: Generated<Timestamp>;
project_id: string | null;
name: string;
value: number;
source: ScoreSource;
@@ -335,6 +356,13 @@ export type Session = {
user_id: string;
expires: Timestamp;
};
export type SsoConfig = {
domain: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
auth_provider: string;
auth_config: unknown | null;
};
export type Trace = {
id: string;
external_id: string | null;
@@ -351,6 +379,8 @@ export type Trace = {
input: unknown | null;
output: unknown | null;
session_id: string | null;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
};
export type TraceSession = {
id: string;
@@ -408,16 +438,19 @@ export type DB = {
events: Events;
job_configurations: JobConfiguration;
job_executions: JobExecution;
llm_api_keys: LlmApiKeys;
membership_invitations: MembershipInvitation;
memberships: Membership;
models: Model;
observations: Observation;
observations_view: ObservationView;
posthog_integrations: PosthogIntegration;
pricings: Pricing;
project_memberships: ProjectMembership;
projects: Project;
prompts: Prompt;
scores: Score;
Session: Session;
sso_configs: SsoConfig;
trace_sessions: TraceSession;
traces: Trace;
traces_view: TraceView;
@@ -0,0 +1,10 @@
-- CreateTable
CREATE TABLE "sso_configs" (
"domain" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"auth_provider" TEXT NOT NULL,
"auth_config" JSONB,
CONSTRAINT "sso_configs_pkey" PRIMARY KEY ("domain")
);
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "models_model_name_idx" ON "models"("model_name");
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "observations_internal_model_idx" ON "observations"("internal_model");
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "dataset_items" ADD COLUMN "metadata" JSONB;
-- AlterTable
ALTER TABLE "datasets" ADD COLUMN "metadata" JSONB;
@@ -0,0 +1,16 @@
-- CreateTable
CREATE TABLE "posthog_integrations" (
"project_id" TEXT NOT NULL,
"encrypted_posthog_api_key" TEXT NOT NULL,
"posthog_host_name" TEXT NOT NULL,
"last_sync_at" TIMESTAMP(3),
"enabled" BOOLEAN NOT NULL,
CONSTRAINT "posthog_integrations_pkey" PRIMARY KEY ("project_id")
);
-- CreateIndex
CREATE INDEX "posthog_integrations_project_id_idx" ON "posthog_integrations"("project_id");
-- AddForeignKey
ALTER TABLE "posthog_integrations" ADD CONSTRAINT "posthog_integrations_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "posthog_integrations" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
@@ -0,0 +1,26 @@
DELETE FROM models WHERE id = 'cluv2t5k3000508ih5kve9zag';
DELETE FROM models WHERE id = 'clrkvq6iq000008ju6c16gynt';
INSERT INTO models (
id,
project_id,
model_name,
match_pattern,
start_date,
input_price,
output_price,
total_price,
unit,
tokenizer_id,
tokenizer_config
)
VALUES
-- updating tokenizer model to gpt-4-turbo-2024-04-09
('cluv2t5k3000508ih5kve9zag', NULL, 'gpt-4-turbo-2024-04-09', '(?i)^(gpt-4-turbo-2024-04-09)$', NULL, 0.00001, 0.00003, NULL, 'TOKENS', 'openai', '{ "tokensPerMessage": 3, "tokensPerName": 1, "tokenizerModel": "gpt-4-turbo-2024-04-09" }'),
-- update gpt-4-1106-preview naming replacing gpt-4-turbo
('clrkvq6iq000008ju6c16gynt', NULL, 'gpt-4-1106-preview', '(?i)^(gpt-4-1106-preview)$', NULL, 0.00001, 0.00003, NULL, 'TOKENS', 'openai', '{ "tokensPerMessage": 3, "tokensPerName": 1, "tokenizerModel": "gpt-4-1106-preview" }'),
-- apparently azure supports gpt-4-preview
('clv2o2x0p000008jsf9afceau', NULL, ' gpt-4-preview', '(?i)^(gpt-4-preview)$', NULL, 0.00001, 0.00003, NULL, 'TOKENS', 'openai', '{ "tokensPerMessage": 3, "tokensPerName": 1, "tokenizerModel": "gpt-4-turbo-preview" }')
@@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE "llm_api_keys" (
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"provider" TEXT NOT NULL,
"display_secret_key" TEXT NOT NULL,
"secret_key" TEXT NOT NULL,
"project_id" TEXT NOT NULL,
CONSTRAINT "llm_api_keys_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "llm_api_keys_id_key" ON "llm_api_keys"("id");
-- CreateIndex
CREATE INDEX "llm_api_keys_project_id_provider_idx" ON "llm_api_keys"("project_id", "provider");
-- CreateIndex
CREATE UNIQUE INDEX "llm_api_keys_project_id_provider_key" ON "llm_api_keys"("project_id", "provider");
-- AddForeignKey
ALTER TABLE "llm_api_keys" ADD CONSTRAINT "llm_api_keys_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "job_executions_project_id_status_idx" ON "job_executions"("project_id", "status");
@@ -0,0 +1,16 @@
BEGIN;
-- Step 1: Alter the 'prompts' table to add the 'labels' column
ALTER TABLE prompts
ADD COLUMN labels TEXT[] DEFAULT ARRAY[]::TEXT[];
-- Step 2: Update the 'labels' column to include 'production' for active prompts
UPDATE prompts
SET labels = array_append(labels, 'production')
WHERE is_active = TRUE;
-- Step 3: Drop the required constraint on 'is_active' column.
ALTER TABLE prompts
ALTER COLUMN is_active DROP NOT NULL;
COMMIT;
@@ -0,0 +1,15 @@
UPDATE
prompts
SET
labels = array_append(labels, 'latest')
WHERE
id = (
SELECT
id
FROM
prompts AS p2
WHERE
p2.name = prompts.name
ORDER BY
created_at DESC
LIMIT 1);
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "traces" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "traces_created_at_idx" ON "traces"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "traces_updated_at_idx" ON "traces"("updated_at");
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "scores" ADD COLUMN "project_id" TEXT;
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "scores_project_id_idx" ON "scores"("project_id");
@@ -0,0 +1,5 @@
-- Backfill project_id on existing scores based on linked trace_id
UPDATE scores
SET project_id = traces.project_id
FROM traces
WHERE scores.trace_id = traces.id AND scores.project_id IS NULL;
@@ -0,0 +1,15 @@
-- Rename table, will cause a downtime as it needs to be locked and synced with TRPC API
ALTER TABLE "memberships"
RENAME TO "project_memberships";
-- AlterTable
ALTER TABLE "project_memberships" RENAME CONSTRAINT "memberships_pkey" TO "project_memberships_pkey";
-- RenameForeignKey
ALTER TABLE "project_memberships" RENAME CONSTRAINT "memberships_project_id_fkey" TO "project_memberships_project_id_fkey";
-- RenameForeignKey
ALTER TABLE "project_memberships" RENAME CONSTRAINT "memberships_user_id_fkey" TO "project_memberships_user_id_fkey";
-- RenameIndex
ALTER INDEX "memberships_user_id_idx" RENAME TO "project_memberships_user_id_idx";
@@ -0,0 +1,37 @@
-- Goal: Rename the enum `MembershipRole` to `ProjectRole` and update all tables that use it
-- Create a new Enum
CREATE TYPE "ProjectRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER', 'VIEWER');
-- Add a temporary column to tables (for each table)
ALTER TABLE "audit_logs" ADD COLUMN "user_project_role_temp" text;
ALTER TABLE "membership_invitations" ADD COLUMN "role_temp" text;
ALTER TABLE "project_memberships" ADD COLUMN "role_temp" text;
-- Copy data over to temporary columns (for each table)
UPDATE "audit_logs" SET "user_project_role_temp" = "user_project_role"::text;
UPDATE "membership_invitations" SET "role_temp" = "role"::text;
UPDATE "project_memberships" SET "role_temp" = "role"::text;
-- Drop old columns
ALTER TABLE "audit_logs" DROP COLUMN "user_project_role";
ALTER TABLE "membership_invitations" DROP COLUMN "role";
ALTER TABLE "project_memberships" DROP COLUMN "role";
-- Rename temporary columns to old column names
ALTER TABLE "audit_logs" RENAME COLUMN "user_project_role_temp" TO "user_project_role";
ALTER TABLE "membership_invitations" RENAME COLUMN "role_temp" TO "role";
ALTER TABLE "project_memberships" RENAME COLUMN "role_temp" TO "role";
-- Convert the text columns to enum columns (for each table)
ALTER TABLE "audit_logs" ALTER COLUMN "user_project_role" TYPE "ProjectRole" USING "user_project_role"::"ProjectRole";
ALTER TABLE "membership_invitations" ALTER COLUMN "role" TYPE "ProjectRole" USING "role"::"ProjectRole";
ALTER TABLE "project_memberships" ALTER COLUMN "role" TYPE "ProjectRole" USING "role"::"ProjectRole";
-- Make the columns NOT NULL (for each table)
ALTER TABLE "audit_logs" ALTER COLUMN "user_project_role" SET NOT NULL;
ALTER TABLE "membership_invitations" ALTER COLUMN "role" SET NOT NULL;
ALTER TABLE "project_memberships" ALTER COLUMN "role" SET NOT NULL;
-- Now finally, you can drop your old enum
DROP TYPE "MembershipRole";
@@ -0,0 +1,20 @@
INSERT INTO models (
id,
project_id,
model_name,
match_pattern,
start_date,
input_price,
output_price,
total_price,
unit,
tokenizer_id,
tokenizer_config
)
VALUES
-- gpt-4o
('b9854a5c92dc496b997d99d20', NULL, 'gpt-4o', '(?i)^(gpt-4o)$', NULL, 0.000005, 0.000015, NULL, 'TOKENS', 'openai', '{ "tokensPerMessage": 3, "tokensPerName": 1, "tokenizerModel": "gpt-4o" }'),
-- gpt-4o-2024-05-13
('b9854a5c92dc496b997d99d21', NULL, 'gpt-4o-2024-05-13', '(?i)^(gpt-4o-2024-05-13)$', NULL, 0.000005, 0.000015, NULL, 'TOKENS', 'openai', '{ "tokensPerMessage": 3, "tokensPerName": 1, "tokenizerModel": "gpt-4o-2024-05-13" }')
+123 -64
View File
@@ -3,7 +3,7 @@
generator client {
provider = "prisma-client-js"
previewFeatures = ["tracing", "views"]
previewFeatures = ["tracing", "views", "relationJoins"]
}
datasource db {
@@ -63,21 +63,21 @@ model Session {
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
password String?
image String?
admin Boolean @default(false)
accounts Account[]
sessions Session[]
memberships Membership[]
invitations MembershipInvitation[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
featureFlags String[] @default([]) @map("feature_flags")
AuditLog AuditLog[]
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
password String?
image String?
admin Boolean @default(false)
accounts Account[]
sessions Session[]
projectMemberships ProjectMembership[]
invitations MembershipInvitation[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
featureFlags String[] @default([]) @map("feature_flags")
AuditLog AuditLog[]
@@map("users")
}
@@ -92,25 +92,27 @@ model VerificationToken {
}
model Project {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
name String
cloudConfig Json? @map("cloud_config") // Langfuse Cloud, for zod schema see projectsRouter.ts
members Membership[]
traces Trace[]
observations Observation[]
apiKeys ApiKey[]
dataset Dataset[]
RawEvents Events[]
invitations MembershipInvitation[]
sessions TraceSession[]
Prompt Prompt[]
Model Model[]
AuditLog AuditLog[]
EvalTemplate EvalTemplate[]
JobConfiguration JobConfiguration[]
JobExecution JobExecution[]
id String @id @default(cuid())
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
name String
cloudConfig Json? @map("cloud_config") // Langfuse Cloud, for zod schema see projectsRouter.ts
projectMembers ProjectMembership[]
traces Trace[]
observations Observation[]
apiKeys ApiKey[]
dataset Dataset[]
RawEvents Events[]
invitations MembershipInvitation[]
sessions TraceSession[]
Prompt Prompt[]
Model Model[]
AuditLog AuditLog[]
EvalTemplate EvalTemplate[]
JobConfiguration JobConfiguration[]
JobExecution JobExecution[]
LlmApiKeys LlmApiKeys[]
PosthogIntegration PosthogIntegration[]
@@map("projects")
}
@@ -135,37 +137,54 @@ model ApiKey {
@@map("api_keys")
}
model Membership {
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
userId String @map("user_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role MembershipRole
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
model LlmApiKeys {
id String @id @unique @default(cuid())
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
provider String
displaySecretKey String @map("display_secret_key")
secretKey String @map("secret_key")
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@unique([projectId, provider])
@@index([projectId, provider])
@@map("llm_api_keys")
}
model ProjectMembership {
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
userId String @map("user_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role ProjectRole
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
@@id([projectId, userId])
@@index([userId])
@@map("memberships")
@@map("project_memberships")
}
model MembershipInvitation {
id String @id @unique @default(cuid())
id String @id @unique @default(cuid())
email String
role MembershipRole
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
senderId String? @map("sender_id")
sender User? @relation(fields: [senderId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
role ProjectRole
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
senderId String? @map("sender_id")
sender User? @relation(fields: [senderId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
@@index([projectId])
@@index([email])
@@map("membership_invitations")
}
enum MembershipRole {
enum ProjectRole {
OWNER
ADMIN
MEMBER
@@ -208,6 +227,8 @@ model Trace {
output Json?
sessionId String? @map("session_id")
session TraceSession? @relation(fields: [sessionId, projectId], references: [id, projectId])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
scores Score[]
DatasetRunItems DatasetRunItems[]
@@ -222,6 +243,8 @@ model Trace {
@@index([externalId])
@@index(timestamp)
@@index(release)
@@index(updatedAt)
@@index(createdAt)
@@index([tags(ops: ArrayOps)], type: Gin)
@@map("traces")
}
@@ -302,6 +325,7 @@ model Observation {
@@index(projectId)
@@index(parentObservationId)
@@index(model)
@@index(internalModel)
@@index(promptId)
@@index([projectId, startTime, type])
@@map("observations")
@@ -368,6 +392,7 @@ enum ObservationLevel {
model Score {
id String @id @default(cuid())
timestamp DateTime @default(now())
projectId String? @map("project_id")
name String
value Float
source ScoreSource
@@ -381,6 +406,7 @@ model Score {
@@unique([id, traceId]) // used for upsert
@@index(timestamp)
@@index([value])
@@index([projectId])
@@index([traceId], type: Hash)
@@index([observationId], type: Hash)
@@index([source])
@@ -429,6 +455,7 @@ model Dataset {
id String @id @default(cuid())
name String
description String?
metadata Json?
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
@@ -446,6 +473,7 @@ model DatasetItem {
status DatasetStatus @default(ACTIVE)
input Json?
expectedOutput Json? @map("expected_output")
metadata Json?
sourceTraceId String? @map("source_trace_id")
sourceTrace Trace? @relation(fields: [sourceTraceId], references: [id], onDelete: SetNull)
sourceObservationId String? @map("source_observation_id")
@@ -531,9 +559,10 @@ model Prompt {
name String
version Int
type String @default("text")
isActive Boolean @map("is_active")
isActive Boolean? @map("is_active") // Deprecated. To be removed once 'production' labels work as expected.
config Json @default("{}")
tags String[] @default([])
labels String[] @default([])
Observation Observation[]
@@unique([projectId, name, version])
@@ -566,20 +595,21 @@ model Model {
@@unique([projectId, modelName, startDate, unit])
@@index([projectId, modelName, startDate, unit])
@@index([projectId, modelName])
@@index(modelName)
@@map("models")
}
model AuditLog {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
userId String @map("user_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
userProjectRole MembershipRole @map("user_project_role")
resourceType String @map("resource_type")
resourceId String @map("resource_id")
id String @id @default(cuid())
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
userId String @map("user_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
userProjectRole ProjectRole @map("user_project_role")
resourceType String @map("resource_type")
resourceId String @map("resource_id")
action String
before String? //stringified JSON
after String? // stringified JSON
@@ -676,7 +706,36 @@ model JobExecution {
jobOutputScoreId String? @map("job_output_score_id")
score Score? @relation(fields: [jobOutputScoreId], references: [id], onDelete: SetNull) // job remains when scores are deleted
@@index([projectId, status])
@@index([projectId, id])
@@index([projectId])
@@map("job_executions")
}
// Single Sign-On configuration for a domain
// This feature is part of the Enterprise Edition
model SsoConfig {
domain String @id @default(cuid()) // e.g. "google.com"
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
authProvider String @map("auth_provider") // e.g. "okta", ee/sso/types.ts
authConfig Json? @map("auth_config")
// e.g. { "clientId": "1234", "clientSecret": "5678" }, null if credentials from env should be used
// secrets like clientSecret are encrypted on the application level
@@map("sso_configs")
}
model PosthogIntegration {
projectId String @id @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
encryptedPosthogApiKey String @map("encrypted_posthog_api_key")
posthogHostName String @map("posthog_host_name")
lastSyncAt DateTime? @map("last_sync_at")
enabled Boolean
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId])
@@map("posthog_integrations")
}
+18 -13
View File
@@ -51,7 +51,7 @@ async function main() {
create: {
id: seedProjectId,
name: "llm-app",
members: {
projectMembers: {
create: {
role: "OWNER",
userId: user.id,
@@ -72,7 +72,7 @@ async function main() {
name: "summary-prompt",
project: { connect: { id: seedProjectId } },
prompt: "prompt {{variable}} {{anotherVariable}}",
isActive: true,
labels: ["production", "latest"],
version: 1,
createdBy: "user-1",
},
@@ -110,7 +110,7 @@ async function main() {
create: {
id: "239ad00f-562f-411d-af14-831c75ddd875",
name: "demo-app",
members: {
projectMembers: {
create: {
role: "OWNER",
userId: user.id,
@@ -240,6 +240,7 @@ async function main() {
description:
datasetNumber === 0 ? "Dataset test description" : undefined,
projectId: project2.id,
metadata: datasetNumber === 0 ? { key: "value" } : undefined,
},
});
@@ -268,6 +269,7 @@ async function main() {
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,
metadata: Math.random() > 0.5 ? { key: "value" } : undefined,
},
});
datasetItemIds.push(datasetItem.id);
@@ -499,6 +501,7 @@ function createObjects(
value: Math.floor(Math.random() * 3) - 1,
timestamp: traceTs,
source: ScoreSource.REVIEW,
projectId,
},
]
: []),
@@ -510,6 +513,7 @@ function createObjects(
value: Math.floor(Math.random() * 10) - 5,
timestamp: traceTs,
source: ScoreSource.API,
projectId,
},
]
: []),
@@ -691,6 +695,7 @@ function createObjects(
observationId: generation.id,
traceId: trace.id,
source: ScoreSource.API,
projectId: trace.projectId,
});
if (Math.random() > 0.6)
scores.push({
@@ -699,6 +704,7 @@ function createObjects(
observationId: generation.id,
traceId: trace.id,
source: ScoreSource.API,
projectId: trace.projectId,
});
for (let l = 0; l < Math.floor(Math.random() * 2); l++) {
@@ -762,7 +768,7 @@ async function generatePrompts(project: Project) {
prompt: "Prompt 1 content",
name: "Prompt 1",
version: 1,
isActive: true,
labels: ["production", "latest"],
},
{
id: `prompt-${v4()}`,
@@ -771,7 +777,7 @@ async function generatePrompts(project: Project) {
prompt: "Prompt 2 content",
name: "Prompt 2",
version: 1,
isActive: true,
labels: ["production", "latest"],
},
{
id: `prompt-${v4()}`,
@@ -780,7 +786,7 @@ async function generatePrompts(project: Project) {
prompt: "Prompt 3 content",
name: "Prompt 3 by API",
version: 1,
isActive: true,
labels: ["production", "latest"],
},
{
id: `prompt-${v4()}`,
@@ -789,7 +795,7 @@ async function generatePrompts(project: Project) {
prompt: "Prompt 4 content",
name: "Prompt 4",
version: 1,
isActive: true,
labels: ["production", "latest"],
tags: ["tag1", "tag2"],
},
];
@@ -810,7 +816,7 @@ async function generatePrompts(project: Project) {
prompt: prompt.prompt,
name: prompt.name,
version: prompt.version,
isActive: prompt.isActive,
labels: prompt.labels,
tags: prompt.tags,
},
update: {
@@ -831,7 +837,6 @@ async function generatePrompts(project: Project) {
temperature: 0.7,
},
version: 1,
isActive: false,
},
{
id: `prompt-${v4()}`,
@@ -844,7 +849,7 @@ async function generatePrompts(project: Project) {
topP: 0.9,
},
version: 2,
isActive: true,
labels: ["production"],
},
{
id: `prompt-${v4()}`,
@@ -858,7 +863,7 @@ async function generatePrompts(project: Project) {
frequencyPenalty: 0.5,
},
version: 3,
isActive: false,
labels: ["production", "latest"],
},
];
@@ -879,7 +884,7 @@ async function generatePrompts(project: Project) {
name: version.name,
config: version.config,
version: version.version,
isActive: version.isActive,
labels: version.labels,
},
update: {
id: version.id,
@@ -908,7 +913,7 @@ async function generatePrompts(project: Project) {
prompt: `${promptName} version ${i} content`,
name: promptName,
version: i,
isActive: i === 20,
labels: i === 20 ? ["production", "latest"] : [],
},
update: {
id: promptId,
+58
View File
@@ -0,0 +1,58 @@
import crypto from "crypto";
import { env } from "../env";
const ENCRYPTION_KEY: string = env.ENCRYPTION_KEY || ""; // Must be 256 bits (32 bytes, 64 hex characters)
const IV_LENGTH: number = 16; // For AES, this is always 16
// Alternatively: openssl rand -hex 32
export function keyGen() {
return crypto.randomBytes(32).toString("hex");
}
/**
* Encrypts the given plain text using AES-256-GCM algorithm.
*
* @param {string} plainText - The text to encrypt.
* @param {string} [keyHex=ENCRYPTION_KEY] - The encryption key in hexadecimal format.
* @returns {string} The encrypted data in hex format, including IV and authentication tag.
*/
export function encrypt(
plainText: string,
keyHex: string = ENCRYPTION_KEY
): string {
const iv = crypto.randomBytes(IV_LENGTH); // Directly use Buffer returned by randomBytes
const cipher = crypto.createCipheriv(
"aes-256-gcm",
Buffer.from(keyHex, "hex"),
iv
);
let encrypted = cipher.update(plainText, "utf8", "hex");
encrypted += cipher.final("hex");
const authTag = cipher.getAuthTag();
// Return iv, encrypted data, and authTag as hex, combined in one line
return iv.toString("hex") + ":" + encrypted + ":" + authTag.toString("hex");
}
export function decrypt(text: string, keyHex: string = ENCRYPTION_KEY): string {
const [ivHex, encryptedHex, authTagHex] = text.split(":");
if (!ivHex || !encryptedHex || !authTagHex) {
throw new Error("Invalid or corrupted cipher format");
}
const iv = Buffer.from(ivHex, "hex");
const encryptedText = Buffer.from(encryptedHex, "hex");
const authTag = Buffer.from(authTagHex, "hex");
const decipher = crypto.createDecipheriv(
"aes-256-gcm",
Buffer.from(keyHex, "hex"),
iv
);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encryptedText, undefined, "utf8");
decrypted += decipher.final("utf8");
return decrypted.toString();
}
+13
View File
@@ -0,0 +1,13 @@
import { z } from "zod";
const EnvSchema = z.object({
ENCRYPTION_KEY: z
.string()
.length(
64,
"ENCRYPTION_KEY must be 256 bits, 64 string characters in hex format, generate via: openssl rand -hex 32"
)
.optional(),
});
export const env = EnvSchema.parse(process.env);
+7
View File
@@ -0,0 +1,7 @@
import { BaseError } from "./BaseError";
export class ApiError extends BaseError {
constructor(description = "Api call failed") {
super("ApiError", 500, description, true);
}
}
@@ -7,7 +7,7 @@ export class BaseError extends Error {
name: string,
httpCode: number,
description: string,
isOperational: boolean,
isOperational: boolean
) {
super(description);
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
@@ -0,0 +1,7 @@
import { BaseError } from "./BaseError";
export class LangfuseNotFoundError extends BaseError {
constructor(description = "Not Found") {
super("LangfuseNotFoundError", 404, description, true);
}
}
@@ -1,6 +1,7 @@
export { BaseError } from "./BaseError";
export { NotFoundError } from "./NotFoundError";
export { LangfuseNotFoundError } from "./NotFoundError";
export { ValidationError } from "./ValidationError";
export { UnauthorizedError } from "./UnauthorizedError";
export { ForbiddenError } from "./ForbiddenError";
export { MethodNotAllowedError } from "./MethodNotAllowedError";
export { ApiError } from "./ApiError";
+32 -23
View File
@@ -1,5 +1,5 @@
import z from "zod";
import { ModelProvider, observationsTableCols } from "../..";
import { ModelProvider } from "../..";
export const langfuseObjects = [
"trace",
@@ -34,6 +34,17 @@ export const wipVariableMapping = z.object({
selectedColumnId: z.string().nullish(),
});
const observationCols = [
{
name: "Metadata",
id: "metadata",
type: "stringObject",
internal: 'o."metadata"',
},
{ name: "Input", id: "input", internal: 'o."input"' },
{ name: "Output", id: "output", internal: 'o."output"' },
];
export const availableEvalVariables = [
{
id: "trace",
@@ -52,32 +63,18 @@ export const availableEvalVariables = [
{
id: "span",
display: "Span",
availableColumns: [
{
name: "Metadata",
id: "metadata",
type: "stringObject",
internal: 'o."metadata"',
},
{ name: "Input", id: "input", internal: 'o."input"' },
{ name: "Output", id: "output", internal: 'o."output"' },
],
availableColumns: observationCols,
},
{
id: "generation",
display: "Generation",
availableColumns: [
{
name: "Metadata",
id: "metadata",
type: "stringObject",
internal: 'o."metadata"',
},
{ name: "Input", id: "input", internal: 'o."input"' },
{ name: "Output", id: "output", internal: 'o."output"' },
],
availableColumns: observationCols,
},
{
id: "event",
display: "Event",
availableColumns: observationCols,
},
{ id: "event", display: "Event", availableColumns: observationsTableCols },
];
export const evalLLMModels = [
@@ -96,10 +93,22 @@ export const evalLLMModels = [
maxTemperature: 2,
max_tokens: 256,
top_p: 1,
} as const,
{
provider: ModelProvider.OpenAI,
model: "gpt-4o",
temperature: 1,
maxTemperature: 2,
max_tokens: 256,
top_p: 1,
},
] as const;
export const EvalModelNames = z.enum(["gpt-3.5-turbo", "gpt-4-turbo-preview"]);
export const EvalModelNames = z.enum([
"gpt-3.5-turbo",
"gpt-4-turbo-preview",
"gpt-4o",
]);
export const OutputSchema = z.object({
reasoning: z.string(),
+4
View File
@@ -9,6 +9,7 @@ export * from "./filterToPrisma";
export * from "./tracesTable";
export * from "./server/auth";
export * from "./observationsTable";
export { env } from "./env";
// llm api
export * from "./server/llm/types";
@@ -20,3 +21,6 @@ export * from "./features/evals/types";
// export db types only
export * from "@prisma/client";
export { type DB } from "../prisma/generated/types";
// errors
export * from "./errors/index";
+1
View File
@@ -15,6 +15,7 @@ export function getDisplaySecretKey(secretKey: string) {
}
export async function hashSecretKey(key: string) {
// legacy, uses bcrypt, transformed into hashed key upon first use
const hashedKey = await hash(key, 11);
return hashedKey;
}
@@ -4,6 +4,7 @@ import {
HumanMessage,
SystemMessage,
} from "@langchain/core/messages";
import type { BaseCallbackHandler } from "@langchain/core/callbacks/base";
import {
BytesOutputParser,
StringOutputParser,
@@ -25,6 +26,8 @@ type LLMCompletionParams = {
messages: ChatMessage[];
modelParams: ModelParams;
functionCall?: LLMFunctionCall;
callbacks?: BaseCallbackHandler[];
apiKey?: string;
};
type FetchLLMCompletionParams = LLMCompletionParams & {
@@ -34,14 +37,12 @@ type FetchLLMCompletionParams = LLMCompletionParams & {
export async function fetchLLMCompletion(
params: LLMCompletionParams & {
streaming: true;
functionCall: undefined;
}
): Promise<IterableReadableStream<Uint8Array>>;
export async function fetchLLMCompletion(
params: LLMCompletionParams & {
streaming: false;
functionCall: undefined;
}
): Promise<string>;
@@ -55,7 +56,8 @@ export async function fetchLLMCompletion(
export async function fetchLLMCompletion(
params: FetchLLMCompletionParams
): Promise<string | IterableReadableStream<Uint8Array> | unknown> {
const { messages, modelParams, streaming } = params;
// the apiKey must never be printed to the console
const { messages, modelParams, streaming, callbacks, apiKey } = params;
const finalMessages = messages.map((message) => {
if (message.role === ChatMessageRole.User)
return new HumanMessage(message.content);
@@ -68,20 +70,26 @@ export async function fetchLLMCompletion(
let chatModel: ChatOpenAI | ChatAnthropic;
if (modelParams.provider === ModelProvider.Anthropic) {
chatModel = new ChatAnthropic({
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
anthropicApiKey: apiKey,
modelName: modelParams.model,
temperature: modelParams.temperature,
maxTokens: modelParams.max_tokens,
topP: modelParams.top_p,
callbacks,
});
} else if (modelParams.provider === ModelProvider.OpenAI) {
chatModel = new ChatOpenAI({
openAIApiKey: apiKey,
modelName: modelParams.model,
temperature: modelParams.temperature,
maxTokens: modelParams.max_tokens,
topP: modelParams.top_p,
callbacks,
});
} else {
chatModel = new ChatOpenAI({
openAIApiKey: process.env.OPENAI_API_KEY,
modelName: modelParams.model,
temperature: modelParams.temperature,
maxTokens: modelParams.max_tokens,
topP: modelParams.top_p,
});
// eslint-disable-next-line no-unused-vars
const _exhaustiveCheck: never = modelParams;
throw new Error("This model provider is not supported.");
}
console.log("Making LLM call with params: ", modelParams);
+3
View File
@@ -13,6 +13,7 @@ export enum ModelProvider {
Anthropic = "anthropic",
OpenAI = "openai",
}
export enum ChatMessageRole {
System = "system",
User = "user",
@@ -41,6 +42,8 @@ export type OpenAIModelParams = {
} & ModelConfig;
export const openAIModels = [
"gpt-4o",
"gpt-4o-2024-05-13",
"gpt-4-turbo-preview",
"gpt-4-1106-preview",
"gpt-4-0613",
+48 -36
View File
@@ -1,8 +1,13 @@
import { ObservationLevel } from "@prisma/client";
import { ColumnDefinition, OptionsDefinition } from ".";
export const tracesTableCols: ColumnDefinition[] = [
{ name: "⭐️", id: "bookmarked", type: "boolean", internal: "t.bookmarked" },
const tracesOnlyCols: ColumnDefinition[] = [
{
name: "⭐️",
id: "bookmarked",
type: "boolean",
internal: "t.bookmarked",
},
{ name: "ID", id: "id", type: "string", internal: "t.id" },
{
name: "Name",
@@ -24,6 +29,41 @@ export const tracesTableCols: ColumnDefinition[] = [
type: "string",
internal: 't."session_id"',
},
{
name: "Metadata",
id: "metadata",
type: "stringObject",
internal: 't."metadata"',
},
{
name: "Version",
id: "version",
type: "string",
internal: 't."version"',
},
{
name: "Release",
id: "release",
type: "string",
internal: 't."release"',
},
{
name: "Level",
id: "level",
type: "stringOptions",
internal: '"level"',
options: Object.values(ObservationLevel).map((value) => ({ value })),
},
{
name: "Tags",
id: "tags",
type: "arrayOptions",
internal: 't."tags"',
options: [], // to be filled in at runtime
},
];
export const tracesTableCols: ColumnDefinition[] = [
...tracesOnlyCols,
{
name: "Input Tokens",
id: "inputTokens",
@@ -48,12 +88,7 @@ export const tracesTableCols: ColumnDefinition[] = [
type: "number",
internal: 'tm."totalTokens"',
},
{
name: "Metadata",
id: "metadata",
type: "stringObject",
internal: 't."metadata"',
},
{
name: "Scores",
id: "scores_avg",
@@ -84,34 +119,10 @@ export const tracesTableCols: ColumnDefinition[] = [
type: "number",
internal: '"calculatedTotalCost"',
},
{
name: "Version",
id: "version",
type: "string",
internal: 't."version"',
},
{
name: "Release",
id: "release",
type: "string",
internal: 't."release"',
},
{
name: "Level",
id: "level",
type: "stringOptions",
internal: '"level"',
options: Object.values(ObservationLevel).map((value) => ({ value })),
},
{
name: "Tags",
id: "tags",
type: "arrayOptions",
internal: 't."tags"',
options: [], // to be filled in at runtime
},
];
export const evalTableCols: ColumnDefinition[] = tracesOnlyCols;
export type TraceOptions = {
scores_avg: Array<string>;
name: Array<OptionsDefinition>;
@@ -119,9 +130,10 @@ export type TraceOptions = {
};
export function tracesTableColsWithOptions(
options?: TraceOptions
options?: TraceOptions,
cols: ColumnDefinition[] = tracesTableCols
): ColumnDefinition[] {
return tracesTableCols.map((col) => {
return cols.map((col) => {
if (col.id === "scores_avg") {
return { ...col, keyOptions: options?.scores_avg ?? [] };
}
+1 -1
View File
@@ -7,7 +7,7 @@
"outDir": "./dist",
"types": ["node"],
"target": "ES2020",
"rootDir": ".",
"rootDir": "."
},
"include": ["."],
"exclude": ["node_modules", "dist"]
+1521 -1168
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -3,3 +3,4 @@ packages:
- "web"
- "worker"
- "packages/**"
- "ee"
+2 -1
View File
@@ -5,10 +5,11 @@ FROM node:20-alpine AS alpine
RUN apk update && apk upgrade --no-cache libcrypto3 libssl3 libc6-compat
FROM alpine AS base
RUN npm install pnpm turbo --global
RUN npm install turbo --global
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@8.15.5 --activate
FROM base AS pruner
+30 -27
View File
@@ -1,14 +1,13 @@
{
"name": "web",
"version": "2.26.0",
"version": "2.40.0",
"private": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"scripts": {
"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",
"build": "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",
@@ -23,15 +22,16 @@
"dependencies": {
"@anthropic-ai/tokenizer": "^0.0.4",
"@aws-sdk/client-s3": "^3.550.0",
"@aws-sdk/lib-storage": "^3.550.0",
"@aws-sdk/lib-storage": "^3.568.0",
"@aws-sdk/s3-request-presigner": "^3.554.0",
"@codemirror/lang-json": "^6.0.1",
"@headlessui/react": "^1.7.18",
"@headlessui/react": "^1.7.19",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.3.4",
"@langchain/anthropic": "^0.1.14",
"@langchain/core": "^0.1.57",
"@langchain/anthropic": "^0.1.16",
"@langchain/core": "^0.1.61",
"@langchain/openai": "^0.0.28",
"@langfuse/ee": "workspace:*",
"@langfuse/shared": "workspace:*",
"@marsidev/react-turnstile": "^0.5.4",
"@next-auth/prisma-adapter": "^1.0.7",
@@ -55,15 +55,16 @@
"@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.16",
"@react-email/render": "^0.0.12",
"@react-email/components": "^0.0.17",
"@react-email/render": "^0.0.13",
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@sentry/nextjs": "^7.110.0",
"@sentry/node": "^7.110.0",
"@sentry/profiling-node": "^7.110.0",
"@sentry/types": "^7.110.0",
"@sentry/nextjs": "^7.113.0",
"@sentry/node": "^7.113.0",
"@sentry/profiling-node": "^7.113.0",
"@sentry/types": "^7.113.0",
"@t3-oss/env-nextjs": "^0.8.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.11.8",
@@ -74,34 +75,35 @@
"@trpc/server": "^10.45.0",
"@uiw/codemirror-theme-github": "^4.21.25",
"@uiw/react-codemirror": "^4.21.25",
"ai": "^3.0.22",
"ai": "^3.0.23",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"core-js": "^3.36.1",
"cors": "^2.8.5",
"date-fns": "^3.3.1",
"decimal.js": "^10.4.3",
"dompurify": "^3.1.2",
"exponential-backoff": "^3.1.1",
"js-tiktoken": "^1.0.11",
"js-tiktoken": "^1.0.12",
"kysely": "^0.27.3",
"langchain": "^0.1.33",
"langchain": "^0.1.36",
"lodash": "^4.17.21",
"lucide-react": "^0.364.0",
"next": "^14.2.1",
"next": "^14.2.3",
"next-auth": "^4.24.7",
"next-query-params": "^5.0.0",
"nodemailer": "^6.9.13",
"posthog-js": "^1.105.9",
"posthog-js": "^1.122.0",
"posthog-node": "^3.6.3",
"prisma": "^5.12.1",
"prisma": "^5.13.0",
"react": "18.2.0",
"react-day-picker": "^8.10.0",
"react-day-picker": "^8.10.1",
"react-dom": "18.2.0",
"react-hook-form": "^7.51.3",
"react-icons": "^5.0.1",
"react-responsive": "^9.0.2",
"react-responsive": "^10.0.0",
"react18-json-view": "^0.2.8-canary.6",
"sonner": "^1.4.41",
"superjson": "2.2.1",
@@ -116,29 +118,30 @@
"@mermaid-js/mermaid-cli": "^10.7.0",
"@playwright/test": "^1.43.1",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.2",
"@testing-library/react": "^15.0.6",
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
"@types/dompurify": "^3.0.5",
"@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.78",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^7.6.0",
"@typescript-eslint/parser": "^7.7.0",
"autoprefixer": "^10.4.19",
"dotenv-cli": "^7.3.0",
"eslint": "^8.56.0",
"eslint-config-next": "^14.2.1",
"eslint-config-next": "^14.2.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"node-mocks-http": "^1.14.1",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.13",
"prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.3",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
-1
View File
@@ -1 +0,0 @@
openapi*.yml
@@ -6,13 +6,23 @@ info:
## Authentication
Authenticate with the API using Basic Auth, get API keys in the project
settings:
Authenticate with the API using [Basic
Auth](https://en.wikipedia.org/wiki/Basic_access_authentication), get API
keys in the project settings:
- username: Langfuse Public Key
- password: Langfuse Secret Key
## Exports
- OpenAPI spec: https://cloud.langfuse.com/generated/api/openapi.yml
- Postman collection:
https://cloud.langfuse.com/generated/postman/collection.json
paths:
/api/public/dataset-items:
post:
@@ -482,6 +492,26 @@ paths:
items:
type: string
nullable: true
- name: fromTimestamp
in: query
description: >-
Optional filter to only include traces on or after a certain
datetime (ISO 8601)
required: false
schema:
type: string
format: date-time
nullable: true
- name: toTimestamp
in: query
description: >-
Optional filter to only include traces before a certain datetime
(ISO 8601)
required: false
schema:
type: string
format: date-time
nullable: true
responses:
'200':
description: ''
@@ -572,14 +602,16 @@ paths:
parameters:
- name: page
in: query
description: page number, starts at 1
description: Page number, starts at 1.
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
description: limit of items per page
description: >-
Limit of items per page. If you encounter api issues due to too
large page sizes, try to reduce the limit.
required: false
schema:
type: integer
@@ -618,7 +650,7 @@ paths:
in: query
description: >-
Retrieve only observations with a start_time greater than this
timestamp.
datetime (ISO 8601).
required: false
schema:
type: string
@@ -697,19 +729,100 @@ paths:
application/json:
schema: {}
security: *ref_0
/api/public/prompts:
/api/public/v2/prompts/{promptName}:
get:
description: Get a prompt
operationId: prompts_get
tags:
- Prompts
parameters:
- name: name
in: query
- name: promptName
in: path
description: The name of the prompt
required: true
schema:
type: string
- name: version
in: query
description: Version of the prompt to be retrieved.
required: false
schema:
type: integer
nullable: true
- name: label
in: query
description: >-
Label of the prompt to be retrieved. Defaults to "production" if no
label or version is set.
required: false
schema:
type: string
nullable: true
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Prompt'
'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/v2/prompts:
get:
description: Get a list of prompt names with versions and labels
operationId: prompts_list
tags:
- Prompts
parameters:
- name: name
in: query
required: false
schema:
type: string
nullable: true
- name: label
in: query
required: false
schema:
type: string
nullable: true
- name: tag
in: query
required: false
schema:
type: string
nullable: true
- name: page
in: query
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
required: false
schema:
@@ -721,7 +834,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/Prompt'
$ref: '#/components/schemas/PromptMetaListResponse'
'400':
description: ''
content:
@@ -847,14 +960,16 @@ paths:
parameters:
- name: page
in: query
description: page number, starts at 1
description: Page number, starts at 1.
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
description: limit of items per page
description: >-
Limit of items per page. If you encounter api issues due to too
large page sizes, try to reduce the limit.
required: false
schema:
type: integer
@@ -873,12 +988,33 @@ paths:
nullable: true
- name: fromTimestamp
in: query
description: Retrieve only scores newer than this timestamp.
description: Retrieve only scores newer than this datetime (ISO 8601).
required: false
schema:
type: string
format: date-time
nullable: true
- name: source
in: query
description: Retrieve only scores from a specific source.
required: true
schema:
$ref: '#/components/schemas/ScoreSource'
- name: operator
in: query
description: Retrieve only scores with <operator> value.
required: false
schema:
type: string
nullable: true
- name: value
in: query
description: Retrieve only scores with <operator> value.
required: false
schema:
type: number
format: double
nullable: true
responses:
'200':
description: ''
@@ -1093,21 +1229,23 @@ paths:
security: *ref_0
/api/public/traces:
get:
description: Get list of traces
description: Get list of traces.
operationId: trace_list
tags:
- Trace
parameters:
- name: page
in: query
description: page number, starts at 1
description: Page number, starts at 1
required: false
schema:
type: integer
nullable: true
- name: limit
in: query
description: limit of items per page
description: >-
Limit of items per page. If you encounter api issues due to too
large page sizes, try to reduce the limit.
required: false
schema:
type: integer
@@ -1126,7 +1264,7 @@ paths:
nullable: true
- name: fromTimestamp
in: query
description: Retrieve only traces newer than this timestamp.
description: Retrieve only traces newer than this datetime (ISO 8601).
required: false
schema:
type: string
@@ -1490,6 +1628,8 @@ components:
description:
type: string
nullable: true
metadata:
nullable: true
projectId:
type: string
createdAt:
@@ -1553,6 +1693,8 @@ components:
nullable: true
expectedOutput:
nullable: true
metadata:
nullable: true
sourceTraceId:
type: string
nullable: true
@@ -1685,6 +1827,7 @@ components:
enum:
- API
- REVIEW
- EVAL
CreateDatasetItemRequest:
title: CreateDatasetItemRequest
type: object
@@ -1695,6 +1838,14 @@ components:
nullable: true
expectedOutput:
nullable: true
metadata:
nullable: true
sourceTraceId:
type: string
nullable: true
sourceObservationId:
type: string
nullable: true
id:
type: string
nullable: true
@@ -1750,6 +1901,8 @@ components:
description:
type: string
nullable: true
metadata:
nullable: true
required:
- name
HealthResponse:
@@ -2337,6 +2490,8 @@ components:
type: string
countTraces:
type: integer
countObservations:
type: integer
totalCost:
type: number
format: double
@@ -2347,6 +2502,7 @@ components:
required:
- date
- countTraces
- countObservations
- totalCost
- usage
UsageByModel:
@@ -2358,6 +2514,7 @@ components:
properties:
model:
type: string
nullable: true
inputUsage:
type: integer
outputUsage:
@@ -2365,7 +2522,6 @@ components:
totalUsage:
type: integer
required:
- model
- inputUsage
- outputUsage
- totalUsage
@@ -2416,6 +2572,42 @@ components:
required:
- id
- name
PromptMetaListResponse:
title: PromptMetaListResponse
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/PromptMeta'
meta:
$ref: '#/components/schemas/utilsMetaResponse'
required:
- data
- meta
PromptMeta:
title: PromptMeta
type: object
properties:
name:
type: string
versions:
type: array
items:
type: integer
labels:
type: array
items:
type: string
tags:
type: array
items:
type: string
required:
- name
- versions
- labels
- tags
CreatePromptRequest:
title: CreatePromptRequest
oneOf:
@@ -2447,18 +2639,19 @@ components:
properties:
name:
type: string
isActive:
type: boolean
description: Should the prompt be promoted to production immediately?
prompt:
type: array
items:
$ref: '#/components/schemas/ChatMessage'
config:
nullable: true
labels:
type: array
items:
type: string
nullable: true
required:
- name
- isActive
- prompt
CreateTextPromptRequest:
title: CreateTextPromptRequest
@@ -2466,16 +2659,17 @@ components:
properties:
name:
type: string
isActive:
type: boolean
description: Should the prompt be promoted to production immediately?
prompt:
type: string
config:
nullable: true
labels:
type: array
items:
type: string
nullable: true
required:
- name
- isActive
- prompt
Prompt:
title: Prompt
@@ -2511,10 +2705,15 @@ components:
version:
type: integer
config: {}
labels:
type: array
items:
type: string
required:
- name
- version
- config
- labels
ChatMessage:
title: ChatMessage
type: object
@@ -2,7 +2,7 @@
"info": {
"name": "Langfuse",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"description": "## Authentication\n\nAuthenticate with the API using Basic Auth, get API keys in the project settings:\n\n- username: Langfuse Public Key\n- password: Langfuse Secret Key"
"description": "## Authentication\n\nAuthenticate with the API using [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication), get API keys in the project settings:\n\n- username: Langfuse Public Key\n- password: Langfuse Secret Key\n\n## Exports\n\n- OpenAPI spec: https://cloud.langfuse.com/generated/api/openapi.yml\n- Postman collection: https://cloud.langfuse.com/generated/postman/collection.json"
},
"variable": [
{
@@ -65,7 +65,7 @@
"auth": null,
"body": {
"mode": "raw",
"raw": "{\n \"datasetName\": \"example\",\n \"input\": \"UNKNOWN\",\n \"expectedOutput\": \"UNKNOWN\",\n \"id\": \"example\"\n}",
"raw": "{\n \"datasetName\": \"example\",\n \"input\": \"UNKNOWN\",\n \"expectedOutput\": \"UNKNOWN\",\n \"metadata\": \"UNKNOWN\",\n \"sourceTraceId\": \"example\",\n \"sourceObservationId\": \"example\",\n \"id\": \"example\"\n}",
"options": {
"raw": {
"language": "json"
@@ -245,7 +245,7 @@
"auth": null,
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"example\",\n \"description\": \"example\"\n}",
"raw": "{\n \"name\": \"example\",\n \"description\": \"example\",\n \"metadata\": \"UNKNOWN\"\n}",
"options": {
"raw": {
"language": "json"
@@ -379,7 +379,7 @@
"request": {
"description": "Get daily metrics of the Langfuse project",
"url": {
"raw": "{{baseUrl}}/api/public/metrics/daily?page=&limit=&traceName=&userId=&tags=",
"raw": "{{baseUrl}}/api/public/metrics/daily?page=&limit=&traceName=&userId=&tags=&fromTimestamp=&toTimestamp=",
"host": [
"{{baseUrl}}"
],
@@ -414,6 +414,16 @@
"key": "tags",
"value": "",
"description": "Optional filter for metrics where traces include all of these tags"
},
{
"key": "fromTimestamp",
"value": "",
"description": "Optional filter to only include traces on or after a certain datetime (ISO 8601)"
},
{
"key": "toTimestamp",
"value": "",
"description": "Optional filter to only include traces before a certain datetime (ISO 8601)"
}
],
"variable": []
@@ -483,12 +493,12 @@
{
"key": "page",
"value": "",
"description": "page number, starts at 1"
"description": "Page number, starts at 1."
},
{
"key": "limit",
"value": "",
"description": "limit of items per page"
"description": "Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit."
},
{
"key": "name",
@@ -518,7 +528,7 @@
{
"key": "fromStartTime",
"value": "",
"description": "Retrieve only observations with a start_time greater than this timestamp."
"description": "Retrieve only observations with a start_time greater than this datetime (ISO 8601)."
}
],
"variable": []
@@ -575,13 +585,58 @@
"request": {
"description": "Get a prompt",
"url": {
"raw": "{{baseUrl}}/api/public/prompts?name=&version=",
"raw": "{{baseUrl}}/api/public/v2/prompts/:promptName?version=&label=",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"public",
"v2",
"prompts",
":promptName"
],
"query": [
{
"key": "version",
"value": "",
"description": "Version of the prompt to be retrieved."
},
{
"key": "label",
"value": "",
"description": "Label of the prompt to be retrieved. Defaults to \"production\" if no label or version is set."
}
],
"variable": [
{
"key": "promptName",
"value": "",
"description": "The name of the prompt"
}
]
},
"header": [],
"method": "GET",
"auth": null,
"body": null
},
"response": []
},
{
"_type": "endpoint",
"name": "List",
"request": {
"description": "Get a list of prompt names with versions and labels",
"url": {
"raw": "{{baseUrl}}/api/public/v2/prompts?name=&label=&tag=&page=&limit=",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"public",
"v2",
"prompts"
],
"query": [
@@ -591,7 +646,22 @@
"description": null
},
{
"key": "version",
"key": "label",
"value": "",
"description": null
},
{
"key": "tag",
"value": "",
"description": null
},
{
"key": "page",
"value": "",
"description": null
},
{
"key": "limit",
"value": "",
"description": null
}
@@ -611,13 +681,14 @@
"request": {
"description": "Create a prompt",
"url": {
"raw": "{{baseUrl}}/api/public/prompts",
"raw": "{{baseUrl}}/api/public/v2/prompts",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"public",
"v2",
"prompts"
],
"query": [],
@@ -628,7 +699,7 @@
"auth": null,
"body": {
"mode": "raw",
"raw": "{\n \"type\": \"chat\",\n \"name\": \"example\",\n \"isActive\": true,\n \"prompt\": [\n {\n \"role\": \"example\",\n \"content\": \"example\"\n }\n ],\n \"config\": \"UNKNOWN\"\n}",
"raw": "{\n \"type\": \"chat\",\n \"name\": \"example\",\n \"prompt\": [\n {\n \"role\": \"example\",\n \"content\": \"example\"\n }\n ],\n \"config\": \"UNKNOWN\",\n \"labels\": [\n \"example\"\n ]\n}",
"options": {
"raw": {
"language": "json"
@@ -684,7 +755,7 @@
"request": {
"description": "Get a list of scores",
"url": {
"raw": "{{baseUrl}}/api/public/scores?page=&limit=&userId=&name=&fromTimestamp=",
"raw": "{{baseUrl}}/api/public/scores?page=&limit=&userId=&name=&fromTimestamp=&source=&operator=&value=",
"host": [
"{{baseUrl}}"
],
@@ -697,12 +768,12 @@
{
"key": "page",
"value": "",
"description": "page number, starts at 1"
"description": "Page number, starts at 1."
},
{
"key": "limit",
"value": "",
"description": "limit of items per page"
"description": "Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit."
},
{
"key": "userId",
@@ -717,7 +788,22 @@
{
"key": "fromTimestamp",
"value": "",
"description": "Retrieve only scores newer than this timestamp."
"description": "Retrieve only scores newer than this datetime (ISO 8601)."
},
{
"key": "source",
"value": "",
"description": "Retrieve only scores from a specific source."
},
{
"key": "operator",
"value": "",
"description": "Retrieve only scores with <operator> value."
},
{
"key": "value",
"value": "",
"description": "Retrieve only scores with <operator> value."
}
],
"variable": []
@@ -875,7 +961,7 @@
"_type": "endpoint",
"name": "List",
"request": {
"description": "Get list of traces",
"description": "Get list of traces.",
"url": {
"raw": "{{baseUrl}}/api/public/traces?page=&limit=&userId=&name=&fromTimestamp=&orderBy=&tags=",
"host": [
@@ -890,12 +976,12 @@
{
"key": "page",
"value": "",
"description": "page number, starts at 1"
"description": "Page number, starts at 1"
},
{
"key": "limit",
"value": "",
"description": "limit of items per page"
"description": "Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit."
},
{
"key": "userId",
@@ -910,7 +996,7 @@
{
"key": "fromTimestamp",
"value": "",
"description": "Retrieve only traces newer than this timestamp."
"description": "Retrieve only traces newer than this datetime (ISO 8601)."
},
{
"key": "orderBy",
+18 -8
View File
@@ -62,6 +62,7 @@ export async function modelMatch() {
where: {
internalModel: null,
type: "GENERATION",
model: { not: null },
},
take: BATCH_SIZE,
skip: index * BATCH_SIZE,
@@ -235,14 +236,23 @@ export async function modelMatch() {
}
}
await prisma.observation.updateMany({
where: {
internalModel: "LANGFUSETMPNOMODEL",
},
data: {
internalModel: null,
},
});
let updatedCount;
do {
console.log(`Updating LANGFUSETMPNOMODEL ${updatedCount}`);
const result = await prisma.$queryRaw<[{ id: string }]>`
WITH to_update AS (
SELECT id
FROM observations
WHERE internal_model = 'LANGFUSETMPNOMODEL'
AND "type" = 'GENERATION'
LIMIT 50000
)
UPDATE observations
set internal_model = NULL
WHERE id IN (SELECT id FROM to_update)
RETURNING id;`;
updatedCount = result.length;
} while (updatedCount > 0);
const end = Date.now();
+3 -3
View File
@@ -15,13 +15,12 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
tracesSampler: (samplingContext: SamplingContext) => {
if (
samplingContext.request &&
samplingContext.request.method === "POST" &&
samplingContext.request.url &&
samplingContext.request.url.includes("api/trpc")
) {
return 0.9;
return 0.3;
}
return 0.15;
return 0.1;
},
profilesSampleRate: 0.2, // Profiling sample rate is relative to tracesSampleRate
@@ -29,6 +28,7 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
// Add profiling integration to list of integrations
new ProfilingIntegration(),
new Sentry.Integrations.Prisma({ client: prisma }),
Sentry.metrics.metricsAggregatorIntegration(),
],
// filter out passwords from the signup request body
+40 -5
View File
@@ -10,7 +10,7 @@ test("should redirect to home if signed in", async ({ page }) => {
await page.goto("/auth/sign-in");
await page.fill('input[name="email"]', "demo@langfuse.com");
await page.fill('input[type="password"]', "password");
await page.click('button[type="submit"]');
await page.click('button[data-testid="submit-email-password-sign-in-form"]');
// wait 2 seconds
await page.waitForTimeout(2000);
@@ -32,7 +32,7 @@ test("Successfully sign up & able to go to homepage", async ({ page }) => {
await page.fill('input[name="name"]', "demo lang");
await page.fill('input[name="email"]', randomEmailAddress());
await page.fill('input[type="password"]', "password2");
await page.click('button[type="submit"]');
await page.click('button[data-testid="submit-email-password-sign-up-form"]');
await page.waitForTimeout(2000);
// see get started page
await expect(page).toHaveURL("/?getStarted=1");
@@ -45,7 +45,7 @@ test("Successfully sign up & able to go to homepage with uppercase email", async
await page.fill('input[name="name"]', "demo lang");
await page.fill('input[name="email"]', "A" + randomEmailAddress());
await page.fill('input[type="password"]', "password3");
await page.click('button[type="submit"]');
await page.click('button[data-testid="submit-email-password-sign-up-form"]');
await page.waitForTimeout(2000);
// see get started page
await expect(page).toHaveURL("/?getStarted=1");
@@ -55,13 +55,13 @@ test("Signup input validation", async ({ page }) => {
await page.goto("auth/sign-up");
await page.fill('input[name="email"]', "notanemail");
await page.fill('input[type="password"]', "shortPw");
await page.click('button[type="submit"]');
await page.click('button[data-testid="submit-email-password-sign-up-form"]');
await page.waitForTimeout(2000);
await expect(page.getByText("Invalid email")).toBeVisible();
await expect(
page.getByText("Password must be at least 8 characters long"),
).toBeVisible();
await page.click('button[type="submit"]');
await page.click('button[data-testid="submit-email-password-sign-up-form"]');
await page.waitForTimeout(2000);
// don't see get started page
await expect(page).not.toHaveURL("/?getStarted=1");
@@ -70,3 +70,38 @@ test("Signup input validation", async ({ page }) => {
// random email address to be used in tests
const randomEmailAddress = () =>
Math.random().toString(36).substring(2, 11) + "@example.com";
test("Unauthenticated user should be redirected to target URL after login", async ({
page,
}) => {
await page.goto("/auth/sign-in");
await page.fill('input[name="email"]', "demo@langfuse.com");
await page.fill('input[type="password"]', "password");
await page.click('button[data-testid="submit-email-password-sign-in-form"]');
// wait 2 seconds
await page.waitForTimeout(2000);
// project id and prompt from seed.ts
const promptUrl = "/project/7a88fb47-b4e2-43b8-a06c-a5ce950dc53a/prompts/summary-prompt";
await page.getByRole("button", { name: /Demo User/ }).click();
await page.getByRole("menuitem", { name: "Sign Out" }).click();
await expect(page).toHaveURL("/auth/sign-in");
await page.goto(promptUrl);
await page.waitForTimeout(2000);
await expect(page).toHaveURL(/targetPath/);
await page.fill('input[name="email"]', "demo@langfuse.com");
await page.fill('input[type="password"]', "password");
await page.click('button[data-testid="submit-email-password-sign-in-form"]');
await page.waitForTimeout(2000);
await expect(page).toHaveURL(promptUrl);
});
+2 -2
View File
@@ -7,7 +7,7 @@ test("should see new projects dialog open after clicking new project btn", async
await page.fill('input[name="name"]', "demo user");
await page.fill('input[name="email"]', randomEmailAddress());
await page.fill('input[type="password"]', "password");
await page.click('button[type="submit"]');
await page.click('button[data-testid="submit-email-password-sign-up-form"]');
await page.waitForTimeout(2000);
await page.waitForTimeout(2000);
expect(await page.innerHTML("data-testid=create-new-project-title")).toBe(
@@ -24,7 +24,7 @@ test("Create a project with provided name", async ({ page }) => {
await page.goto("/auth/sign-in");
await page.fill('input[name="email"]', "demo@langfuse.com");
await page.fill('input[type="password"]', "password");
await page.click('button[type="submit"]');
await page.click('button[data-testid="submit-email-password-sign-in-form"]');
await page.waitForTimeout(2000);
await page.isVisible('[data-testid="new-project-form"]');
await page.click('[data-testid="create-project-btn"]');
+82 -66
View File
@@ -5,63 +5,12 @@ import { makeAPICall, pruneDatabase } from "@/src/__tests__/test-utils";
import { v4 } from "uuid";
describe("/api/public/datasets and /api/public/dataset-items API Endpoints", () => {
beforeEach(async () => await pruneDatabase());
afterEach(async () => await pruneDatabase());
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({
where: {
name: "dataset-name",
},
});
expect(dbDataset.length).toBeGreaterThan(0);
const getDataset = await makeAPICall(
"GET",
`/api/public/datasets/dataset-name`,
);
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();
const createItemRes = await makeAPICall<{
datasetName: string; // field that can break if the API changes as it is not a db column
}>("POST", "/api/public/dataset-items", {
datasetName: "dataset-name-2",
input: { key: "value" },
expectedOutput: { key: "value" },
id: datasetItemId,
});
expect(createItemRes.status).toBe(200);
expect(createItemRes.body).toMatchObject({
datasetName: "dataset-name-2", // not included in db table
});
const traceId = v4();
const observationId = v4();
const traceId = v4();
const observationId = v4();
beforeEach(async () => {
await pruneDatabase();
// create sample trace and observation
const response = await makeAPICall("POST", "/api/public/ingestion", {
batch: [
{
@@ -97,6 +46,62 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
],
});
expect(response.status).toBe(207);
});
afterEach(async () => await pruneDatabase());
it("should create and get a dataset, include special characters", async () => {
await makeAPICall("POST", "/api/public/datasets", {
name: "dataset + name",
description: "dataset-description",
metadata: { foo: "bar" },
});
const dbDataset = await prisma.dataset.findMany({
where: {
name: "dataset + name",
},
});
expect(dbDataset.length).toBeGreaterThan(0);
const getDataset = await makeAPICall(
"GET",
`/api/public/datasets/${encodeURIComponent("dataset + name")}`,
);
expect(getDataset.status).toBe(200);
expect(getDataset.body).toMatchObject({
name: "dataset + name",
description: "dataset-description",
metadata: { foo: "bar" },
});
});
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();
const createItemRes = await makeAPICall<{
datasetName: string; // field that can break if the API changes as it is not a db column
}>("POST", "/api/public/dataset-items", {
datasetName: "dataset-name-2",
input: { key: "value" },
expectedOutput: { key: "value" },
metadata: { key: "value-dataset-item" },
id: datasetItemId,
});
expect(createItemRes.status).toBe(200);
expect(createItemRes.body).toMatchObject({
datasetName: "dataset-name-2", // not included in db table
});
await makeAPICall("POST", "/api/public/dataset-run-items", {
datasetItemId: datasetItemId,
@@ -136,6 +141,9 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
datasetName: "dataset-name",
input: { key: "value" },
expectedOutput: { key: "value" },
metadata: { key: "value-dataset-item" },
sourceTraceId: traceId,
sourceObservationId: observationId,
});
const dbDatasetItem = await prisma.datasetItem.findFirst({
where: {
@@ -159,6 +167,7 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
id: dbDatasetItem!.id,
input: { key: "value" },
expectedOutput: { key: "value" },
metadata: { key: "value-dataset-item" },
datasetName: "dataset-name", // not included in db table
},
],
@@ -173,7 +182,10 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
id: dbDatasetItem!.id,
input: { key: "value" },
expectedOutput: { key: "value" },
metadata: { key: "value-dataset-item" },
datasetName: "dataset-name", // not included in db table
sourceTraceId: traceId,
sourceObservationId: observationId,
});
});
@@ -186,6 +198,7 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
id: "dataset-item-id",
datasetName: "dataset-name",
input: { key: "value" },
metadata: { key: "value-dataset-item" },
});
expect(item1.status).toBe(200);
expect(item1.body).toMatchObject({
@@ -196,6 +209,7 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
id: "dataset-item-id",
datasetName: "dataset-name",
input: { key: "value2" },
metadata: ["hello-world"],
});
expect(item2.status).toBe(200);
expect(item2.body).toMatchObject({
@@ -208,22 +222,23 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
});
expect(dbDatasetItem).not.toBeNull();
expect(dbDatasetItem?.input).toMatchObject({ key: "value2" });
expect(dbDatasetItem?.metadata).toMatchObject(["hello-world"]);
});
it("should create and get a dataset run", async () => {
it("should create and get a dataset run, include special characters", async () => {
const dataset = await makeAPICall<{ id: string }>(
"POST",
"/api/public/datasets",
{
name: "dataset-name",
name: "dataset name",
},
);
expect(dataset.status).toBe(200);
expect(dataset.body).toMatchObject({
name: "dataset-name",
name: "dataset name",
});
await makeAPICall("POST", "/api/public/dataset-items", {
datasetName: "dataset-name",
datasetName: "dataset name",
id: "dataset-item-id",
input: { key: "value" },
expectedOutput: { key: "value" },
@@ -272,14 +287,14 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
{
datasetItemId: "dataset-item-id",
observationId: observationId,
runName: "run-only-observation",
runName: "run + only + observation",
runDescription: "run-description",
metadata: { key: "value" },
},
);
const dbRunObservation = await prisma.datasetRuns.findFirst({
where: {
name: "run-only-observation",
name: "run + only + observation",
},
include: {
datasetRunItems: true,
@@ -298,21 +313,22 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
const getRunAPI = await makeAPICall(
"GET",
`/api/public/datasets/dataset-name/runs/run-only-observation`,
`/api/public/datasets/${encodeURIComponent("dataset name")}/runs/${encodeURIComponent("run + only + observation")}`,
);
expect(getRunAPI.status).toBe(200);
expect(getRunAPI.body).toMatchObject({
name: "run-only-observation",
name: "run + only + observation",
description: "run-description",
metadata: { key: "value" },
datasetId: dataset.body.id,
datasetName: "dataset-name",
datasetName: "dataset name",
datasetRunItems: expect.arrayContaining([
expect.objectContaining({
datasetItemId: "dataset-item-id",
observationId: observationId,
traceId: traceId,
datasetRunName: "run-only-observation",
datasetRunName: "run + only + observation",
}),
]),
});
+19
View File
@@ -0,0 +1,19 @@
import * as DOMPurify from "dompurify";
// Create test cases
describe("should sanitize dom", () => {
it("should sanitize urls", () => {
const dirty =
"http://www.some.site/page.html?default=<script>alert(document.cookie)</script>";
const cleam = DOMPurify.sanitize(dirty);
expect(cleam).toBe("http://www.some.site/page.html?default=");
});
it("should should not sanitize valid langfuse urls", () => {
const validLangfuseUrl =
"https://cloud.langfuse.com/project/abjdnksadn/traces?filter=timestamp%3Bdatetime%3B%3B%3E%3B2024-04-19T00%253A00%253A00.000Z%2Cid%3Bstring%3B%3Bcontains%3Ba%2Ctimestamp%3Bdatetime%3B%3B%3C%3B2024-04-30T22%253A00%253A00.000Z%2CinputTokens%3Bnumber%3B%3B%3E%3B1";
const cleam = DOMPurify.sanitize(validLangfuseUrl);
expect(cleam).toBe(validLangfuseUrl);
});
});
@@ -0,0 +1,92 @@
/** @jest-environment node */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import type { Session } from "next-auth";
import { pruneDatabase } from "@/src/__tests__/test-utils";
import { ModelProvider } from "@langfuse/shared";
import { prisma } from "@langfuse/shared/src/db";
import { appRouter } from "@/src/server/api/root";
import { createInnerTRPCContext } from "@/src/server/api/trpc";
describe("llmApiKey.all RPC", () => {
const projectId = "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a";
beforeEach(async () => await pruneDatabase());
const session: Session = {
expires: "1",
user: {
id: "user-1",
name: "Demo User",
projects: [
{
id: projectId,
role: "ADMIN",
name: "test",
},
],
featureFlags: {
templateFlag: true,
evals: true,
},
admin: true,
},
};
const ctx = createInnerTRPCContext({ session });
const caller = appRouter.createCaller({ ...ctx, prisma });
it("should create an llm api key", async () => {
const secret = "test-secret";
await caller.llmApiKey.create({
projectId,
secretKey: secret,
provider: ModelProvider.OpenAI,
});
const llmApiKeys = await prisma.llmApiKeys.findMany({
where: {
projectId,
},
});
expect(llmApiKeys.length).toBe(1);
expect(llmApiKeys[0].projectId).toBe(projectId);
expect(llmApiKeys[0].secretKey).not.toBeNull();
expect(llmApiKeys[0].secretKey).not.toEqual(secret);
expect(llmApiKeys[0].provider).toBe(ModelProvider.OpenAI);
// this has to be 3 dots and the last 4 characters of the secret
expect(llmApiKeys[0].displaySecretKey).toMatch(/^...[a-zA-Z0-9]{4}$/);
});
it("should create and get an llm api key", async () => {
const secret = "test-secret";
await caller.llmApiKey.create({
projectId,
secretKey: secret,
provider: ModelProvider.OpenAI,
});
const dbLlmApiKeys = await prisma.llmApiKeys.findMany({
where: {
projectId,
},
});
expect(dbLlmApiKeys.length).toBe(1);
const llmApiKeys = await caller.llmApiKey.all({
projectId,
});
expect(llmApiKeys.data.length).toBe(1);
expect(llmApiKeys.data[0].provider).toBe(ModelProvider.OpenAI);
// this has to be 3 dots and the last 4 characters of the secret
expect(llmApiKeys.data[0].displaySecretKey).toMatch(/^...[a-zA-Z0-9]{4}$/);
// response must not contain the secret key itself
expect(llmApiKeys.data[0]).not.toHaveProperty("secretKey");
});
});
+10 -1
View File
@@ -73,6 +73,15 @@ describe("model match", () => {
input: "I am a prompt",
output: "I am a completion",
},
{
type: "GENERATION",
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
model: "gpt-4o",
startTime: new Date("2024-01-01T00:00:00.000Z"),
unit: ModelUsageUnit.Tokens,
input: "I am a prompt",
output: "I am a completion",
},
],
});
@@ -86,7 +95,7 @@ describe("model match", () => {
expect(observation1?.completionTokens).toEqual(3000);
expect(observation1?.totalTokens).toEqual(0);
expect(observations.length).toEqual(3);
expect(observations.length).toEqual(4);
observations.forEach((observation) => {
expect(observation.internalModel).toBeDefined();
expect(observation.promptTokens).toBeGreaterThan(0);
@@ -5,10 +5,10 @@ import { makeAPICall, pruneDatabase } from "@/src/__tests__/test-utils";
import { v4 as uuidv4, v4 } from "uuid";
import { type Prompt } from "@langfuse/shared";
import {
PromptSchema,
LegacyPromptSchema,
PromptType,
type ValidatedPrompt,
} from "@/src/features/prompts/server/validation";
type LegacyValidatedPrompt,
} from "@/src/features/prompts/server/utils/validation";
describe("/api/public/prompts API Endpoint", () => {
beforeEach(async () => await pruneDatabase());
@@ -21,7 +21,7 @@ describe("/api/public/prompts API Endpoint", () => {
id: promptId,
name: "prompt-name",
prompt: "prompt",
isActive: true,
labels: ["production"],
version: 1,
config: {
temperature: 0.1,
@@ -51,6 +51,51 @@ describe("/api/public/prompts API Endpoint", () => {
expect(fetchedObservations.body.type).toBe("text");
expect(fetchedObservations.body.version).toBe(1);
expect(fetchedObservations.body.isActive).toBe(true);
expect(fetchedObservations.body.labels).toEqual(["production"]);
expect(fetchedObservations.body.createdBy).toBe("user-1");
expect(fetchedObservations.body.config).toEqual({ temperature: 0.1 });
expect(fetchedObservations.body.tags).toEqual([]);
});
it("should fetch a prompt with special character", async () => {
const promptId = uuidv4();
await prisma.prompt.create({
data: {
id: promptId,
name: "prompt + name",
prompt: "prompt",
labels: ["production"],
version: 1,
config: {
temperature: 0.1,
},
project: {
connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" },
},
createdBy: "user-1",
},
});
const fetchedObservations = await makeAPICall(
"GET",
`/api/public/prompts?name=${encodeURIComponent("prompt + name")}&version=1`,
undefined,
);
expect(fetchedObservations.status).toBe(200);
if (!isPrompt(fetchedObservations.body)) {
throw new Error("Expected body to be a prompt");
}
expect(fetchedObservations.body.id).toBe(promptId);
expect(fetchedObservations.body.name).toBe("prompt + name");
expect(fetchedObservations.body.prompt).toBe("prompt");
expect(fetchedObservations.body.type).toBe("text");
expect(fetchedObservations.body.version).toBe(1);
expect(fetchedObservations.body.isActive).toBe(true);
expect(fetchedObservations.body.labels).toEqual(["production"]);
expect(fetchedObservations.body.createdBy).toBe("user-1");
expect(fetchedObservations.body.config).toEqual({ temperature: 0.1 });
expect(fetchedObservations.body.tags).toEqual([]);
@@ -64,7 +109,7 @@ describe("/api/public/prompts API Endpoint", () => {
id: promptId,
name: "prompt-name",
prompt: "prompt",
isActive: false,
labels: [],
version: 1,
config: {
temperature: 0.1,
@@ -94,7 +139,7 @@ describe("/api/public/prompts API Endpoint", () => {
id: promptId,
name: "prompt-name",
prompt: "prompt-one",
isActive: false,
labels: [],
version: 1,
config: {
temperature: 0.1,
@@ -111,7 +156,7 @@ describe("/api/public/prompts API Endpoint", () => {
id: promptTwoId,
name: "prompt-name",
prompt: "prompt",
isActive: true,
labels: ["production"],
version: 2,
config: {
temperature: 0.2,
@@ -140,68 +185,192 @@ describe("/api/public/prompts API Endpoint", () => {
expect(fetchedObservations.body.prompt).toBe("prompt-one");
expect(fetchedObservations.body.version).toBe(1);
expect(fetchedObservations.body.isActive).toBe(false);
expect(fetchedObservations.body.labels).toEqual([]);
expect(fetchedObservations.body.createdBy).toBe("user-1");
expect(fetchedObservations.body.config).toEqual({ temperature: 0.1 });
});
it("should fetch active prompt when multiple exist", async () => {
const promptIdOne = uuidv4();
const promptIdTwo = uuidv4();
await prisma.prompt.create({
data: {
id: promptIdOne,
name: "prompt-name",
prompt: "prompt",
isActive: false,
version: 1,
config: {
temperature: 0.1,
},
project: {
connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" },
},
createdBy: "user-1",
// First prompt is activated
const prompt1 = await makeAPICall("POST", "/api/public/prompts", {
name: "prompt-name",
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
prompt: "prompt1",
isActive: true,
version: 1,
config: {
temperature: 0.1,
},
createdBy: "user-1",
});
await prisma.prompt.create({
data: {
id: promptIdTwo,
name: "prompt-name",
prompt: "prompt",
isActive: true,
version: 2,
config: {
temperature: 0.2,
},
project: {
connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" },
},
createdBy: "user-1",
// Second prompt also activated
const prompt2 = await makeAPICall("POST", "/api/public/prompts", {
name: "prompt-name",
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
prompt: "prompt2",
labels: ["production"],
isActive: true,
version: 2,
config: {
temperature: 0.2,
},
createdBy: "user-1",
});
const fetchedObservations = await makeAPICall(
// Third prompt is deactivated
const prompt3 = await makeAPICall("POST", "/api/public/prompts", {
name: "prompt-name",
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
prompt: "prompt3",
labels: [], // This should be ignored
isActive: false,
version: 3,
config: {
temperature: 0.3,
},
createdBy: "user-1",
});
// Expect the second prompt to be fetched
const fetchedProductionPrompt = await makeAPICall(
"GET",
"/api/public/prompts?name=prompt-name",
undefined,
);
expect(fetchedObservations.status).toBe(200);
expect(fetchedProductionPrompt.status).toBe(200);
if (!isPrompt(fetchedObservations.body)) {
if (!isPrompt(fetchedProductionPrompt.body)) {
throw new Error("Expected body to be a prompt");
}
expect(fetchedObservations.body.id).toBe(promptIdTwo);
expect(fetchedObservations.body.name).toBe("prompt-name");
expect(fetchedObservations.body.prompt).toBe("prompt");
expect(fetchedObservations.body.type).toBe("text");
expect(fetchedObservations.body.version).toBe(2);
expect(fetchedObservations.body.isActive).toBe(true);
expect(fetchedObservations.body.createdBy).toBe("user-1");
expect(fetchedObservations.body.config).toEqual({ temperature: 0.2 });
// @ts-expect-error
expect(fetchedProductionPrompt.body.id).toBe(prompt2.body.id);
expect(fetchedProductionPrompt.body.name).toBe("prompt-name");
expect(fetchedProductionPrompt.body.prompt).toBe("prompt2");
expect(fetchedProductionPrompt.body.type).toBe("text");
expect(fetchedProductionPrompt.body.version).toBe(2);
expect(fetchedProductionPrompt.body.isActive).toBe(true);
expect(fetchedProductionPrompt.body.labels).toEqual(["production"]);
expect(fetchedProductionPrompt.body.createdBy).toBe("API");
expect(fetchedProductionPrompt.body.config).toEqual({ temperature: 0.2 });
// Expect the first prompt to be deactivated
const fetchedOldProductionPrompt = await makeAPICall(
"GET",
"/api/public/prompts?name=prompt-name&version=1",
undefined,
);
expect(fetchedOldProductionPrompt.status).toBe(200);
if (!isPrompt(fetchedOldProductionPrompt.body)) {
throw new Error("Expected body to be a prompt");
}
// @ts-expect-error
expect(fetchedOldProductionPrompt.body.id).toBe(prompt1.body.id);
expect(fetchedOldProductionPrompt.body.name).toBe("prompt-name");
expect(fetchedOldProductionPrompt.body.prompt).toBe("prompt1");
expect(fetchedOldProductionPrompt.body.type).toBe("text");
expect(fetchedOldProductionPrompt.body.version).toBe(1);
expect(fetchedOldProductionPrompt.body.isActive).toBe(false);
expect(fetchedOldProductionPrompt.body.labels).toEqual([]);
expect(fetchedOldProductionPrompt.body.createdBy).toBe("API");
expect(fetchedOldProductionPrompt.body.config).toEqual({
temperature: 0.1,
});
});
it("should correctly handle overwriting labels", async () => {
// First prompt has multiple labels
const prompt1 = await makeAPICall("POST", "/api/public/prompts", {
name: "prompt-name",
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
prompt: "prompt1",
labels: ["production", "staging", "development"],
isActive: true,
version: 1,
config: {
temperature: 0.1,
},
createdBy: "user-1",
});
// Second prompt overwrites production and staging label
const prompt2 = await makeAPICall("POST", "/api/public/prompts", {
name: "prompt-name",
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
prompt: "prompt2",
labels: ["production", "production", "staging"], // Should be deduped
isActive: true,
version: 2,
config: {
temperature: 0.2,
},
createdBy: "user-1",
});
// Third prompt overwrites staging label
const prompt3 = await makeAPICall("POST", "/api/public/prompts", {
name: "prompt-name",
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
prompt: "prompt3",
labels: ["staging"],
isActive: false,
version: 3,
config: {
temperature: 0.3,
},
createdBy: "user-1",
});
// Expect the second prompt to be fetched as default production prompt
const fetchedProductionPrompt = await makeAPICall(
"GET",
"/api/public/prompts?name=prompt-name",
undefined,
);
expect(fetchedProductionPrompt.status).toBe(200);
if (!isPrompt(fetchedProductionPrompt.body)) {
throw new Error("Expected body to be a prompt");
}
// @ts-expect-error
expect(fetchedProductionPrompt.body.id).toBe(prompt2.body.id);
expect(fetchedProductionPrompt.body.labels).toEqual(["production"]); // Only production label should be present
// Expect the first prompt to have only development label
const fetchedFirstPrompt = await makeAPICall(
"GET",
"/api/public/prompts?name=prompt-name&version=1",
undefined,
);
expect(fetchedFirstPrompt.status).toBe(200);
if (!isPrompt(fetchedFirstPrompt.body)) {
throw new Error("Expected body to be a prompt");
}
// @ts-expect-error
expect(fetchedFirstPrompt.body.id).toBe(prompt1.body.id);
expect(fetchedFirstPrompt.body.labels).toEqual(["development"]);
// Expect the third prompt to have only staging label
const fetchedThirdPrompt = await makeAPICall(
"GET",
"/api/public/prompts?name=prompt-name&version=3",
undefined,
);
expect(fetchedThirdPrompt.status).toBe(200);
if (!isPrompt(fetchedThirdPrompt.body)) {
throw new Error("Expected body to be a prompt");
}
// @ts-expect-error
expect(fetchedThirdPrompt.body.id).toBe(prompt3.body.id);
expect(fetchedThirdPrompt.body.labels).toEqual(["staging", "latest"]);
});
it("should create and fetch a prompt", async () => {
@@ -232,6 +401,7 @@ describe("/api/public/prompts API Endpoint", () => {
expect(fetchedObservations.body.type).toBe("text");
expect(fetchedObservations.body.version).toBe(1);
expect(fetchedObservations.body.isActive).toBe(true);
expect(fetchedObservations.body.labels).toEqual(["production", "latest"]);
expect(fetchedObservations.body.createdBy).toBe("API");
expect(fetchedObservations.body.config).toEqual({ temperature: 0.1 });
});
@@ -247,7 +417,7 @@ describe("/api/public/prompts API Endpoint", () => {
id: promptId,
name: "prompt-name",
prompt: "prompt",
isActive: true,
labels: ["production"],
version: 1,
project: {
connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" },
@@ -310,7 +480,7 @@ describe("/api/public/prompts API Endpoint", () => {
id: promptId,
name: "prompt-name",
prompt: "prompt",
isActive: true,
labels: ["production"],
version: 1,
project: {
connect: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" },
@@ -385,6 +555,7 @@ describe("/api/public/prompts API Endpoint", () => {
expect(fetchedObservations.body.type).toBe("text");
expect(fetchedObservations.body.version).toBe(1);
expect(fetchedObservations.body.isActive).toBe(true);
expect(fetchedObservations.body.labels).toEqual(["production", "latest"]);
expect(fetchedObservations.body.createdBy).toBe("API");
expect(fetchedObservations.body.config).toEqual({});
});
@@ -418,6 +589,7 @@ describe("/api/public/prompts API Endpoint", () => {
expect(validatedPrompt.type).toBe("chat");
expect(validatedPrompt.version).toBe(1);
expect(validatedPrompt.isActive).toBe(true);
expect(validatedPrompt.labels).toEqual(["production", "latest"]);
expect(validatedPrompt.createdBy).toBe("API");
expect(validatedPrompt.config).toEqual({});
});
@@ -441,7 +613,7 @@ describe("/api/public/prompts API Endpoint", () => {
);
expect(status).toBe(404);
expect(body).toEqual({
error: "NotFoundError",
error: "LangfuseNotFoundError",
message: "Prompt not found",
});
});
@@ -469,7 +641,7 @@ describe("/api/public/prompts API Endpoint", () => {
);
expect(status).toBe(404);
expect(body).toEqual({
error: "NotFoundError",
error: "LangfuseNotFoundError",
message: "Prompt not found",
});
});
@@ -492,7 +664,7 @@ describe("/api/public/prompts API Endpoint", () => {
);
expect(status).toBe(404);
expect(body).toEqual({
error: "NotFoundError",
error: "LangfuseNotFoundError",
message: "Prompt not found",
});
});
@@ -546,6 +718,7 @@ describe("/api/public/prompts API Endpoint", () => {
expect(validatedPrompt.type).toBe("chat");
expect(validatedPrompt.version).toBe(1);
expect(validatedPrompt.isActive).toBe(true);
expect(validatedPrompt.labels).toEqual(["production", "latest"]);
expect(validatedPrompt.createdBy).toBe("API");
expect(validatedPrompt.config).toEqual({});
@@ -557,15 +730,17 @@ describe("/api/public/prompts API Endpoint", () => {
);
expect(getResponse2.status).toBe(404);
expect(getResponse2.body).toEqual({
error: "NotFoundError",
error: "LangfuseNotFoundError",
message: "Prompt not found",
});
});
});
const isPrompt = (x: unknown): x is Prompt => {
type PromptWithIsActive = Prompt & { isActive: boolean };
const isPrompt = (x: unknown): x is PromptWithIsActive => {
if (typeof x !== "object" || x === null) return false;
const prompt = x as Prompt;
const prompt = x as PromptWithIsActive;
return (
typeof prompt.id === "string" &&
typeof prompt.name === "string" &&
@@ -579,7 +754,9 @@ const isPrompt = (x: unknown): x is Prompt => {
);
};
const validatePrompt = (obj: Record<string, unknown>): ValidatedPrompt => {
const validatePrompt = (
obj: Record<string, unknown>,
): LegacyValidatedPrompt => {
Object.keys(obj).forEach((key) => {
obj[key] =
key === "createdAt" || key === "updatedAt"
@@ -587,5 +764,5 @@ const validatePrompt = (obj: Record<string, unknown>): ValidatedPrompt => {
: obj[key];
});
return PromptSchema.parse(obj);
return LegacyPromptSchema.parse(obj);
};
File diff suppressed because it is too large Load Diff
+277 -2
View File
@@ -5,8 +5,13 @@ import { makeAPICall, pruneDatabase } from "@/src/__tests__/test-utils";
import { v4 as uuidv4 } from "uuid";
describe("/api/public/scores API Endpoint", () => {
beforeEach(async () => await pruneDatabase());
afterEach(async () => await pruneDatabase());
let should_prune_db = true;
beforeEach(async () => {
if (should_prune_db) await pruneDatabase();
});
afterEach(async () => {
if (should_prune_db) await pruneDatabase();
});
it("should create score for a trace", async () => {
await pruneDatabase();
@@ -316,4 +321,274 @@ describe("/api/public/scores API Endpoint", () => {
observationId: generationId,
});
});
describe("should Filter scores", () => {
const userId = "user-name";
const scoreName = "score-name";
const queryUserName = `userId=${userId}&name=${scoreName}`;
const traceId = uuidv4();
const generationId = uuidv4();
const scoreId_1 = uuidv4();
const scoreId_2 = uuidv4();
const scoreId_3 = uuidv4();
interface GetScoresAPIResponse {
data: [
{
id: string;
name: string;
value: number;
},
];
meta: object;
}
beforeAll(async () => {
should_prune_db = false;
await pruneDatabase();
await makeAPICall("POST", "/api/public/traces", {
id: traceId,
userId: userId,
});
await makeAPICall("POST", "/api/public/generations", {
id: generationId,
});
await makeAPICall("POST", "/api/public/scores", {
id: scoreId_1,
observationId: generationId,
name: scoreName,
value: 10.5,
traceId: traceId,
comment: "comment",
});
await makeAPICall("POST", "/api/public/scores", {
id: scoreId_2,
observationId: generationId,
name: scoreName,
value: 50.5,
traceId: traceId,
comment: "comment",
});
await makeAPICall("POST", "/api/public/scores", {
id: scoreId_3,
observationId: generationId,
name: scoreName,
value: 100.8,
traceId: traceId,
comment: "comment",
});
});
afterAll(async () => {
await pruneDatabase();
});
it("get all scores", async () => {
const getAllScore = await makeAPICall<{
data: [
{
traceId: string;
observationId: string;
},
];
meta: object;
}>("GET", `/api/public/scores?${queryUserName}`);
expect(getAllScore.status).toBe(200);
expect(getAllScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 3,
totalPages: 1,
});
for (const val of getAllScore.body.data) {
expect(val).toMatchObject({
traceId: traceId,
observationId: generationId,
});
}
});
it("test only operator", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&operator=<`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 3,
totalPages: 1,
});
});
it("test only value", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&value=0.8`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 3,
totalPages: 1,
});
});
it("test operator <", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&operator=<&value=50`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 1,
totalPages: 1,
});
expect(getScore.body.data).toMatchObject([
{
id: scoreId_1,
name: scoreName,
value: 10.5,
},
]);
});
it("test operator >", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&operator=>&value=100`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 1,
totalPages: 1,
});
expect(getScore.body.data).toMatchObject([
{
id: scoreId_3,
name: scoreName,
value: 100.8,
},
]);
});
it("test operator <=", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&operator=<=&value=50.5`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 2,
totalPages: 1,
});
expect(getScore.body.data).toMatchObject([
{
id: scoreId_1,
name: scoreName,
value: 10.5,
},
{
id: scoreId_2,
name: scoreName,
value: 50.5,
},
]);
});
it("test operator >=", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&operator=>=&value=50.5`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 2,
totalPages: 1,
});
expect(getScore.body.data).toMatchObject([
{
id: scoreId_2,
name: scoreName,
value: 50.5,
},
{
id: scoreId_3,
name: scoreName,
value: 100.8,
},
]);
});
it("test operator !=", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&operator=!=&value=50.5`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 2,
totalPages: 1,
});
expect(getScore.body.data).toMatchObject([
{
id: scoreId_1,
name: scoreName,
value: 10.5,
},
{
id: scoreId_3,
name: scoreName,
value: 100.8,
},
]);
});
it("test operator =", async () => {
const getScore = await makeAPICall<GetScoresAPIResponse>(
"GET",
`/api/public/scores?${queryUserName}&operator==&value=50.5`,
);
expect(getScore.status).toBe(200);
expect(getScore.body.meta).toMatchObject({
page: 1,
limit: 50,
totalItems: 1,
totalPages: 1,
});
expect(getScore.body.data).toMatchObject([
{
id: scoreId_2,
name: scoreName,
value: 50.5,
},
]);
});
it("test invalid operator", async () => {
const getScore = await makeAPICall(
"GET",
`/api/public/scores?${queryUserName}&operator=op&value=50.5`,
);
expect(getScore.status).toBe(400);
expect(getScore.body).toMatchObject({
message: "Invalid request data",
});
});
it("test invalid value", async () => {
const getScore = await makeAPICall(
"GET",
`/api/public/scores?${queryUserName}&operator=<&value=myvalue`,
);
expect(getScore.status).toBe(400);
expect(getScore.body).toMatchObject({
message: "Invalid request data",
});
});
});
});
@@ -23,6 +23,7 @@ describe("Traces TRPC Router", () => {
],
featureFlags: {
templateFlag: true,
evals: true,
},
admin: true,
},
+2 -1
View File
@@ -12,6 +12,7 @@ export const pruneDatabase = async () => {
await prisma.prompt.deleteMany();
await prisma.events.deleteMany();
await prisma.model.deleteMany();
await prisma.llmApiKeys.deleteMany();
};
export function createBasicAuthHeader(
@@ -89,7 +90,7 @@ export const setupUserAndProject = async () => {
},
],
},
members: {
projectMembers: {
create: {
role: "OWNER",
userId: user.id,
+1
View File
@@ -33,6 +33,7 @@ describe("Token Count Functions", () => {
{ model: "claude", tokenizer: "claude", tokens: 118 },
{ model: "claude-instant-1.2", tokenizer: "claude", tokens: 118 },
{ model: "gpt-3.5-turbo-1106", tokenizer: "openai", tokens: 114 },
{ model: "gpt-4o-2024-05-13", tokenizer: "openai", tokens: 112 },
].forEach(({ model, tokens, tokenizer }) => {
it(`should return token count ${tokens} for ${model}`, () => {
const result = tokenCount({
@@ -5,7 +5,7 @@ import { ChatMessageRole, type ChatMessageWithId } from "@langfuse/shared";
import { Button } from "@/src/components/ui/button";
import { Card, CardContent } from "@/src/components/ui/card";
import { Textarea } from "@/src/components/ui/textarea";
import type { MessagesContext } from "@/src/features/playground/client/components/Messages";
import type { MessagesContext } from "./types";
type ChatMessageProps = Pick<
MessagesContext,
@@ -1,10 +1,12 @@
import { PlusCircleIcon } from "lucide-react";
import { useEffect, useRef } from "react";
import { Button } from "@/src/components/ui/button";
import { ChatMessageComponent } from "@/src/features/playground/client/components/ChatMessageComponent";
import type { MessagesContext } from "@/src/features/playground/client/components/Messages";
import { ChatMessageRole } from "@langfuse/shared";
import { useRef, useEffect } from "react";
import { ChatMessageComponent } from "./ChatMessageComponent";
import type { MessagesContext } from "./types";
type ChatMessagesProps = MessagesContext;
export const ChatMessages: React.FC<ChatMessagesProps> = (props) => {
+13
View File
@@ -0,0 +1,13 @@
import type { ChatMessageRole, ChatMessageWithId } from "@langfuse/shared";
export type MessagesContext = {
messages: ChatMessageWithId[];
addMessage: (role: ChatMessageRole, content?: string) => ChatMessageWithId;
deleteMessage: (id: string) => void;
updateMessage: <Key extends keyof ChatMessageWithId>(
id: string,
key: Key,
value: ChatMessageWithId[Key]
) => void;
};
+9 -3
View File
@@ -1,4 +1,4 @@
import { AlertTriangle } from "lucide-react";
import { AlertTriangle, Check } from "lucide-react";
import { VERSION } from "@/src/constants";
import { env } from "@/src/env.mjs";
@@ -42,10 +42,16 @@ export const LangfuseLogo = ({
"flex items-center gap-2 self-stretch rounded-md px-3 py-2 text-xs ring-1 lg:-mx-2",
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "STAGING"
? "bg-blue-100 text-blue-500 ring-blue-500"
: "bg-red-100 text-red-500 ring-red-500",
: env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "DEV"
? "bg-green-100 text-green-500 ring-green-500"
: "bg-red-100 text-red-500 ring-red-500",
)}
>
<AlertTriangle size={16} />
{env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "DEV" ? (
<Check size={16} />
) : (
<AlertTriangle size={16} />
)}
<span className="whitespace-nowrap">
{["EU", "US"].includes(env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION)
? `PROD-${env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION}`

Some files were not shown because too many files have changed in this diff Show More