Compare commits

...
199 Commits
Author SHA1 Message Date
Max Deichmann b6e49b440e chore: release v2.84.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-10-09 20:57:25 -07:00
marliessophieandGitHub ff6e0648f3 chore: allow updating annotation queue name (#3687) 2024-10-09 23:01:14 +00:00
c4fa31be9f feat(annotation): add annotation queue in feature flagged beta release (#3499)
Co-authored-by: Marc Klingen <git@marcklingen.com>
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-10-09 23:59:24 +02:00
e68394abc6 chore: upgrade nextjs to 14.2.14 (#3643)
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-10-09 13:30:54 -07:00
Max DeichmannandGitHub c24032391d fix: do not retry when api key is missing for evals (#3680) 2024-10-09 18:28:18 +00:00
Max DeichmannandGitHub 6c7faf61f8 fix: adjust queue wait time metrics (#3677)
fix
2024-10-09 14:47:47 +00:00
Marc KlingenandGitHub 43bedf2afe fix: encode all links to objects with user-provided ids (#3672) 2024-10-09 08:59:01 +00:00
Marc KlingenandGitHub 674bdbd976 fix(ui): disable sign-in and sso buttons while email/pw are empty (#3670) 2024-10-09 08:42:41 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ed1f6d8563 chore(deps): bump the observability group with 3 updates (#3657)
Bumps the observability group with 3 updates: [@opentelemetry/resource-detector-aws](https://github.com/open-telemetry/opentelemetry-js-contrib), [@opentelemetry/resource-detector-container](https://github.com/open-telemetry/opentelemetry-js-contrib) and [@opentelemetry/instrumentation-express](https://github.com/open-telemetry/opentelemetry-js-contrib).


Updates `@opentelemetry/resource-detector-aws` from 1.6.1 to 1.6.2
- [Release notes](https://github.com/open-telemetry/opentelemetry-js-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/resource-detector-aws-v1.6.1...resource-detector-aws-v1.6.2)

Updates `@opentelemetry/resource-detector-container` from 0.4.1 to 0.4.2
- [Release notes](https://github.com/open-telemetry/opentelemetry-js-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/instrumentation-tedious-v0.4.1...resource-detector-instana-v0.4.2)

Updates `@opentelemetry/instrumentation-express` from 0.42.0 to 0.43.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-js-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/instrumentation-pg-v0.42.0...instrumentation-pg-v0.43.0)

---
updated-dependencies:
- dependency-name: "@opentelemetry/resource-detector-aws"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: observability
- dependency-name: "@opentelemetry/resource-detector-container"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: observability
- dependency-name: "@opentelemetry/instrumentation-express"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: observability
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 06:52:58 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b49d335eb0 chore(deps): bump @aws-sdk/lib-storage from 3.645.0 to 3.667.0 (#3661)
Bumps [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) from 3.645.0 to 3.667.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.667.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-10-09 06:36:58 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e9bc76fe89 chore(deps): bump @heroicons/react from 2.1.3 to 2.1.5 (#3662)
Bumps [@heroicons/react](https://github.com/tailwindlabs/heroicons) from 2.1.3 to 2.1.5.
- [Release notes](https://github.com/tailwindlabs/heroicons/releases)
- [Changelog](https://github.com/tailwindlabs/heroicons/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/heroicons/compare/v2.1.3...v2.1.5)

---
updated-dependencies:
- dependency-name: "@heroicons/react"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 06:24:35 +00:00
Marc KlingenandGitHub 7cdbc7f464 feat: add crisp events in posthog (#3667)
* feat: add crisp events in posthog

* fix dep

* add comment
2024-10-09 06:12:03 +00:00
237e539c4c fix: add Gitlab Issuer and Account.created_at col (#3651)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-10-09 06:46:17 +02:00
Max DeichmannandGitHub b1357a0bae feat: front-end cloud admin flag for clickhouse (#3666) 2024-10-09 02:37:45 +00:00
Max DeichmannandGitHub fa51789ac2 chore: add projectid to ingestion logs (#3665) 2024-10-09 00:23:27 +00:00
Max DeichmannandGitHub 894ffdda69 chore: remove metrics by users datadog (#3664) 2024-10-08 17:04:31 -07:00
Max DeichmannandGitHub 0c2e6a5b5d chore: add orgid and projectid to ingestion event metric (#3654) 2024-10-08 15:27:07 -07:00
Max DeichmannandGitHub 0d7e5bfcca feat: invalidate API keys via cloud admin api (#3653) 2024-10-08 22:16:48 +00:00
Max DeichmannandGitHub 7cc5a9e229 chore: reduce auth logging (#3652) 2024-10-08 21:54:48 +00:00
629b30e42d fix: properly split tags when passed as a string (#3621) (#3650)
Co-authored-by: Joris ANDRADE <joris@front.com>
2024-10-08 19:00:25 +00:00
Steffen SchmitzandGitHub 3e98df566c chore: update metric name for eval exec queue length (#3649) 2024-10-08 15:50:43 +00:00
Steffen SchmitzandGitHub 0a88294202 chore: use s3 event store to avoid event data in redis (#3537) 2024-10-08 11:05:25 +00:00
Steffen SchmitzandGitHub 6e9f5ace41 chore: update bullmq instrumentation (#3644) 2024-10-08 12:53:48 +02:00
Marc Klingen 1b662c95e5 chore: release v2.83.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-10-08 02:42:25 -07:00
Marc KlingenandGitHub 87e94289ff revert: next upgrade due to incompatibility issues (#3640) 2024-10-08 11:25:41 +02:00
Steffen SchmitzandGitHub ed08331927 chore: remove explicit context propagation for queues (#3622) 2024-10-08 09:16:29 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
02e5c0119a chore(deps-dev): bump prettier from 3.2.5 to 3.3.3 (#3626)
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.5 to 3.3.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.2.5...3.3.3)

---
updated-dependencies:
- dependency-name: prettier
  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-10-08 05:27:30 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
adc39d58e4 chore(deps-dev): bump tsx from 4.19.0 to 4.19.1 (#3627)
Bumps [tsx](https://github.com/privatenumber/tsx) from 4.19.0 to 4.19.1.
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.19.0...v4.19.1)

---
updated-dependencies:
- dependency-name: tsx
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 05:15:27 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
19987e9816 chore(deps): bump @langchain/core from 0.3.3 to 0.3.7 (#3628)
Bumps [@langchain/core](https://github.com/langchain-ai/langchainjs) from 0.3.3 to 0.3.7.
- [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)

---
updated-dependencies:
- dependency-name: "@langchain/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 05:03:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
746299431f chore(deps): bump lucide-react from 0.436.0 to 0.447.0 (#3630)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.436.0 to 0.447.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.447.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  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-10-08 04:51:00 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
74224a04cd chore(deps-dev): bump msw from 2.4.1 to 2.4.9 (#3631)
Bumps [msw](https://github.com/mswjs/msw) from 2.4.1 to 2.4.9.
- [Release notes](https://github.com/mswjs/msw/releases)
- [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mswjs/msw/compare/v2.4.1...v2.4.9)

---
updated-dependencies:
- dependency-name: msw
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 04:38:56 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
874d574a9e chore(deps-dev): bump nodemon from 3.1.3 to 3.1.7 (#3632)
Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.3 to 3.1.7.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v3.1.3...v3.1.7)

---
updated-dependencies:
- dependency-name: nodemon
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 04:22:00 +00:00
Max Deichmann 13655515a2 chore: release v2.83.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-10-07 17:45:14 -07:00
Max DeichmannandGitHub 204ce02bd1 chore: reduce log level (#3636) 2024-10-07 17:41:12 -07:00
Max DeichmannandGitHub b87f84bf67 chore: upgrade webpack (#3633) 2024-10-07 15:03:41 -07:00
Max DeichmannandGitHub e139b40167 chore: upgrade next (#3603) 2024-10-07 19:41:33 +00:00
Steffen SchmitzandGitHub f3596fac10 chore: update GHA checkout and setup-node actions (#3620) 2024-10-07 15:47:22 +02:00
Steffen SchmitzandGitHub f1d15ea268 chore: correlate logs with traces (#3619) 2024-10-07 11:57:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5183cf4859 chore(deps): bump the radix-ui group with 15 updates (#3609)
Bumps the radix-ui group with 15 updates:

| Package | From | To |
| --- | --- | --- |
| [@radix-ui/react-accordion](https://github.com/radix-ui/primitives) | `1.2.0` | `1.2.1` |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-avatar](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-collapsible](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives) | `2.1.1` | `2.1.2` |
| [@radix-ui/react-hover-card](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives) | `1.1.0` | `1.2.0` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives) | `2.1.1` | `2.1.2` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives) | `1.2.0` | `1.2.1` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives) | `1.1.2` | `1.1.3` |


Updates `@radix-ui/react-accordion` from 1.2.0 to 1.2.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-alert-dialog` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-avatar` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-checkbox` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-collapsible` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-dialog` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-dropdown-menu` from 2.1.1 to 2.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-hover-card` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-popover` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-scroll-area` from 1.1.0 to 1.2.0
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-select` from 2.1.1 to 2.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-slider` from 1.2.0 to 1.2.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-switch` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-tabs` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-tooltip` from 1.1.2 to 1.1.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-accordion"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-alert-dialog"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-avatar"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-checkbox"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-collapsible"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-dialog"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-hover-card"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-popover"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-select"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-slider"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-switch"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-tabs"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-tooltip"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 07:46:40 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
604ff3203e chore(deps): bump js-tiktoken from 1.0.12 to 1.0.15 (#3610)
Bumps [js-tiktoken](https://github.com/dqbd/tiktoken) from 1.0.12 to 1.0.15.
- [Release notes](https://github.com/dqbd/tiktoken/releases)
- [Changelog](https://github.com/dqbd/tiktoken/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dqbd/tiktoken/compare/js-tiktoken@1.0.12...js-tiktoken@1.0.15)

---
updated-dependencies:
- dependency-name: js-tiktoken
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 07:25:05 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ca8ba2f233 chore(deps): bump the observability group with 2 updates (#3608)
Bumps the observability group with 2 updates: [@appsignal/opentelemetry-instrumentation-bullmq](https://github.com/appsignal/opentelemetry-instrumentation-bullmq) and [@opentelemetry/api](https://github.com/open-telemetry/opentelemetry-js).


Updates `@appsignal/opentelemetry-instrumentation-bullmq` from 0.7.1 to 0.7.2
- [Changelog](https://github.com/appsignal/opentelemetry-instrumentation-bullmq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/appsignal/opentelemetry-instrumentation-bullmq/commits/v0.7.2)

Updates `@opentelemetry/api` from 1.8.0 to 1.9.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: "@appsignal/opentelemetry-instrumentation-bullmq"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: observability
- dependency-name: "@opentelemetry/api"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: observability
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 07:10:03 +00:00
Max DeichmannandGitHub f9c5534a20 chore: remove auto merge (#3616) 2024-10-06 23:28:57 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f652c75304 chore(deps): bump @uiw/codemirror-theme-tokyo-night from 4.22.2 to 4.23.5 (#3611)
chore(deps): bump @uiw/codemirror-theme-tokyo-night

Bumps [@uiw/codemirror-theme-tokyo-night](https://github.com/uiwjs/react-codemirror) from 4.22.2 to 4.23.5.
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.22.2...v4.23.5)

---
updated-dependencies:
- dependency-name: "@uiw/codemirror-theme-tokyo-night"
  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-10-06 23:23:17 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
879a72b821 chore(deps): bump react-hook-form from 7.51.5 to 7.53.0 (#3613)
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.51.5 to 7.53.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.51.5...v7.53.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  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-10-07 06:08:35 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d3cb8469c0 chore(deps-dev): bump @playwright/test from 1.43.1 to 1.47.2 (#3612)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.43.1 to 1.47.2.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.43.1...v1.47.2)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  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-10-07 02:00:06 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Max Deichmann
b5b93cf710 chore(deps-dev): bump @types/lodash from 4.17.9 to 4.17.10 in the lodash group (#3606)
chore(deps-dev): bump @types/lodash in the lodash group

Bumps the lodash group with 1 update: [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash).


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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-10-07 00:27:53 +00:00
Max DeichmannandGitHub 27caeb1536 chore: fix auto merging (#3614) 2024-10-06 17:13:13 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Max Deichmann
3b40c67aae chore(deps): bump the prisma group with 2 updates (#3604)
* chore(deps): bump the prisma group with 2 updates

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.13.0 to 5.20.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.20.0/packages/cli)

Updates `@prisma/client` from 5.17.0 to 5.20.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.20.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>

* also upgrade docker

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-10-06 16:58:13 -07:00
Max DeichmannandGitHub 79a6bc115f chore: upgrade tailwind and kysely codegen (#3601) 2024-10-06 22:40:16 +00:00
Max DeichmannandGitHub 8aa1e0e1b8 chore: upgrade svelte for ai sdk (#3600) 2024-10-06 11:16:20 -07:00
Max DeichmannandGitHub 217926b049 chore: upgrade mermaid-js (#3599) 2024-10-06 17:56:47 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8ed92de662 chore(deps): bump nodemailer and @types/nodemailer (#3596)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) and [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer). These dependencies needed to be updated together.

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

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

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/nodemailer"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-06 10:23:27 -07:00
Max DeichmannandGitHub 2a9421674b chore: fix dependabot automerge (#3597) 2024-10-06 10:19:45 -07:00
Max DeichmannandGitHub 0b2bf8535d chore: automerge dependabot patch PRs (#3595) 2024-10-06 09:02:18 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b89e2423c3 chore(deps): bump pg and @types/pg (#3493)
Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) and [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg). These dependencies needed to be updated together.

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

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

---
updated-dependencies:
- dependency-name: pg
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/pg"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-06 08:51:48 -07:00
Max DeichmannandGitHub 43340dd6ff chore: setup codeql manually (#3594) 2024-10-06 08:33:37 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e529d887ba chore(deps): bump @mui/x-tree-view from 7.6.2 to 7.19.0 (#3593)
Bumps [@mui/x-tree-view](https://github.com/mui/mui-x/tree/HEAD/packages/x-tree-view) from 7.6.2 to 7.19.0.
- [Release notes](https://github.com/mui/mui-x/releases)
- [Changelog](https://github.com/mui/mui-x/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui/mui-x/commits/v7.19.0/packages/x-tree-view)

---
updated-dependencies:
- dependency-name: "@mui/x-tree-view"
  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-10-05 23:41:42 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d7d79df5cf chore(deps): bump @tanstack/react-table from 8.13.2 to 8.20.5 (#3523)
Bumps [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) from 8.13.2 to 8.20.5.
- [Release notes](https://github.com/TanStack/table/releases)
- [Commits](https://github.com/TanStack/table/commits/v8.20.5/packages/react-table)

---
updated-dependencies:
- dependency-name: "@tanstack/react-table"
  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-10-05 08:18:00 -07:00
Vansh ChopraandGitHub 59de947865 feat: Gitlab Auth Provider (#3564) 2024-10-05 03:37:48 +02:00
Max DeichmannandGitHub c5ab61b356 chore: upgrade vitest (#3588) 2024-10-05 01:12:55 +00:00
Max DeichmannandGitHub 13db6781e2 chore: upgrade sentry (#3587) 2024-10-04 17:59:50 -07:00
Max DeichmannandGitHub 5442429f47 chore: upgrade axios (#3586) 2024-10-04 17:50:34 -07:00
Max DeichmannandGitHub c01600b2b8 chore: upgrade dd-trace (#3584) 2024-10-04 17:32:58 -07:00
Max DeichmannandGitHub 19101723b3 chore: upgrade express (#3583)
something
2024-10-04 17:30:34 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
989522b22a chore(deps-dev): bump vite from 5.2.13 to 5.2.14 (#3572)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.13 to 5.2.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.2.14/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-04 17:00:08 -07:00
Marc Klingen 8b4a2893d5 chore: release v2.82.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-10-03 22:35:10 -07:00
Marc KlingenandGitHub 6719dd4158 feat: init envs via docker compose (#3568)
* feat: allow for use of init envs via docker compose

* add to docker compose build
2024-10-03 08:40:55 +00:00
Marc KlingenandGitHub 94d87f2a42 feat(ui): improve cost and latency rendering on trace tree and preview (#3567) 2024-10-03 02:06:31 +00:00
Hassieb PakzadandGitHub e06272c4df fix(prisma): HMR support (#3547) 2024-10-02 20:45:09 +02:00
Hassieb PakzadandGitHub f6512d7a8e feat(generations-table): show tokens per second (#3511) 2024-10-02 13:42:02 +00:00
Hassieb PakzadandGitHub 5e67d897b7 fix(exports): download link valid few hours only (#3560) 2024-10-02 12:31:34 +00:00
Steffen SchmitzandGitHub 9adcfde4cc chore: consolidate naming pattern for dogstatsd metrics (#3534) 2024-10-01 17:15:26 +02:00
Steffen SchmitzandGitHub bc8f8656a1 chore: disable ddtracing in favour of otel (#3461) 2024-10-01 17:14:45 +02:00
Marc KlingenandGitHub 85a327ef26 fix(ui): tag state inconsistent when navigating between traces (#3544) 2024-10-01 00:00:17 +00:00
Marc Klingen 46b20f4107 chore: release v2.81.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-09-30 16:23:40 -07:00
Marc KlingenandGitHub fb8f26a00e feat(ee): add customizable default model adapter base urls (#3542) 2024-09-30 23:22:45 +00:00
Marc KlingenandGitHub 3d7ba475e2 chore(auth): reduce maxAge of password reset link to 10 minutes (#3541) 2024-09-30 21:40:18 +00:00
Marc KlingenandGitHub 03028a92b4 chore: upgrade fern openapi generator and cli (#3536) 2024-09-30 18:18:53 +02:00
Marc Klingen 8a96290f03 chore: release v2.81.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-09-27 17:05:22 +02:00
Marc KlingenandGitHub bca09bdefe fix(security): only allow redirect via target to internal urls (#3512) 2024-09-27 16:52:58 +02:00
Steffen SchmitzandGitHub 2d314250e9 chore: make credentials for export bucket optional (#3509) 2024-09-27 16:41:54 +02:00
Marc KlingenandGitHub 4a87f402ec fix: favicon reference when using custom basepath (#3506) 2024-09-27 13:10:21 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
800b2136b9 chore(deps-dev): bump @types/lodash from 4.17.7 to 4.17.9 in the lodash group (#3492)
chore(deps-dev): bump @types/lodash in the lodash group

Bumps the lodash group with 1 update: [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash).


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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 10:25:35 +02:00
Steffen SchmitzandGitHub 278df5ab3c feat: optionally upload all incoming events to S3 (#3488) 2024-09-27 06:58:34 +00:00
Marc KlingenandGitHub c7f9e18768 fix(ui): include non-generation warnings/error in rolled-up trace status (#3494) 2024-09-27 00:31:53 +00:00
Max DeichmannandGitHub 2121b40cca security: upgrade snyk (#3482) 2024-09-25 23:10:57 +02:00
Max DeichmannandGitHub 5ba440f2de chore: rate limit legacy endpoints (#3473) 2024-09-25 12:58:13 +00:00
Max DeichmannandGitHub 657a3275de chore: mark admin api as internal and cloud only (#3464) 2024-09-24 12:47:47 +00:00
Steffen SchmitzandGitHub 56bb9c8dcf chore: remove porter configs from CI (#3463) 2024-09-24 11:36:06 +00:00
Steffen SchmitzandGitHub 66e15bcf0d chore: drop redundant indexes on postgres (#3377) 2024-09-24 11:35:04 +02:00
Marc KlingenandGitHub 5f94ca8e82 fix: skip entitlement check for instance admins (#3452) 2024-09-23 16:55:06 +02:00
Steffen SchmitzandGitHub e8561fb105 chore: move web-ee logic from console.log to logger (#3400) 2024-09-23 16:54:45 +02:00
marliessophieandGitHub 18e84c6498 fix(ui_markdown): whitelist images with valid ipv6 or ipv4 hostname (#3444) 2024-09-23 14:12:37 +00:00
Marc KlingenandGitHub 05152ac8d6 fix(auth): email/password sign-up with custom base-path (#3450)
fix: email/password sign-up with custom base-path
2024-09-23 13:48:59 +00:00
marliessophieandGitHub 6cb9e24ba2 chore(ui): support larger step sizes in Lanfguse Trace Timeline View (#3449) 2024-09-23 12:53:33 +00:00
Marc Klingen 484fa424fb docs: update comments on stripe util functions 2024-09-23 14:18:37 +02:00
Hassieb Pakzad 555aebe8c7 chore: release v2.81.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-09-23 13:59:21 +02:00
Max DeichmannandGitHub af29424b48 feat: delete API keys via API (#3448) 2024-09-23 12:30:41 +02:00
Max DeichmannandGitHub 42f1c63bbf chore: set sentry config on build time (#3445) 2024-09-23 11:27:15 +02:00
Hassieb PakzadandGitHub 663ded0743 chore(evals): move to tool calling instead of function calling (#3430) 2024-09-23 11:08:23 +02:00
marliessophieandGitHub d7ced99d16 feat(ui): export generations async on cloud (#3368) 2024-09-23 06:03:03 +00:00
Max DeichmannandGitHub c979cae620 chore: add worker logs for tokenization (#3440) 2024-09-21 16:07:18 +00:00
marliessophieandGitHub c483293b10 style(prompts): display all prompt tags on versions, not just on hover (#3422) 2024-09-20 16:33:10 +00:00
Steffen SchmitzandGitHub 932913cd76 fix: use actual singletons for db clients (#3427) 2024-09-20 13:27:46 +02:00
Steffen SchmitzandGitHub 5875537132 fix: keep more failed jobs in bullmq (#3424) 2024-09-20 13:26:14 +02:00
Marc KlingenandGitHub 1a53981a7a docs: add comment on max aspect ratio of custom logo (#3426) 2024-09-20 09:48:25 +00:00
Hassieb PakzadandGitHub 0e6b676b19 fix(playground): parse complex messages when jumping to playground (#3425) 2024-09-20 11:03:49 +02:00
Marc KlingenandGitHub 955e25555f fix(ui): performance of optimistic updates of bookmarked traces (#3418) 2024-09-19 22:20:11 +00:00
Marc KlingenandGitHub 2a11f1e8fb fix(ui): truncate long model names in dashboard (#3417) 2024-09-19 21:40:54 +00:00
Marc KlingenandGitHub 143a461f0c chore(ui): add link to support and tracing faq to onboarding (#3416) 2024-09-19 19:58:53 +00:00
Max DeichmannandGitHub c8544690f7 chore: remove event io from dd traces (#3413) 2024-09-19 18:06:00 +00:00
Marc Klingen 05a533dd7e chore: release v2.80.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-09-19 16:37:23 +02:00
Steffen SchmitzandGitHub f3d15bb36e feat: optimize daily metrics query (#3371) 2024-09-19 14:10:10 +00:00
Hassieb PakzadandGitHub 24c321f632 fix(dataset-table): cost and latency queries (#3411) 2024-09-19 12:14:42 +00:00
Steffen SchmitzandGitHub 0e9cf548b2 chore: ecs production deploy automation (#3408) 2024-09-19 12:42:21 +02:00
Marc KlingenandGitHub 7158a41fe9 feat(ee): add additional logo (#3404) 2024-09-19 11:45:53 +02:00
Max DeichmannandGitHub bc384a525a chore: adjust sentry sample rate (#3403)
adjust autoscaling eval periods
2024-09-18 22:40:22 +00:00
Max DeichmannandGitHub 3f70828df6 chore: fix dev server speed (#3388) 2024-09-18 17:10:50 +00:00
marliessophieandGitHub 0988635427 fix: prompt tag menu disappears when out of focus (#3394) 2024-09-18 18:39:55 +02:00
Max DeichmannandGitHub 41ed6fed5e fix: allow to eval with azure models (#3387) 2024-09-17 16:54:22 +00:00
Hassieb PakzadandGitHub eb40659e44 fix(playground): save to prompt list (#3384) 2024-09-17 13:54:39 +00:00
Steffen SchmitzandGitHub a22bcb1188 build: add deploy config for eu (#3366) 2024-09-17 10:04:46 +00:00
Marc KlingenandGitHub 392fbe6c8c chore(ui): improve posthog api key error message (#3373)
* chore(ui): improve posthog api key error message

* Empty commit
2024-09-16 21:58:06 +00:00
marliessophieandGitHub 48d5a8f5a1 fix: cutoff filter for sessions export (#3370) 2024-09-16 15:11:32 +00:00
marliessophieandGitHub 44428870f7 style: adjust success toast for dark mode (#3362) 2024-09-16 07:46:10 +00:00
Hassieb PakzadandGitHub 724a4f5d9a fix(ui/prompts): variable parsing for chat prompts (#3360) 2024-09-15 13:55:01 +00:00
Marc KlingenandGitHub 2358264b66 fix(auth): unknown argument refresh_token_expires_in with Github connection (#3356)
fix(auth): unknown argument `refresh_token_expires_in` with Github connection

Fixes langfuse/langfuse#3355
2024-09-13 19:10:44 +00:00
Marc Klingen 65e3b4c5ca chore: release v2.79.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-09-13 20:37:58 +02:00
62c413faf5 fix(ui): ensure table footer is fixed on the bottom of the page (#3354)
* style(tables): ensure footer is fixed on the bottom

* fix

---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-09-13 20:13:36 +02:00
Marc KlingenandGitHub 8ea14ac537 feat(cloud): alert when free plan limit is approaching or reached (#3350)
* feat(cloud): alert when free plan limit is approaching or reached

* check entitlement
2024-09-13 20:08:32 +02:00
marliessophieandGitHub c32b84663c feat(ui): export traces (#3332) 2024-09-13 17:51:22 +00:00
Marc KlingenandGitHub 23ab79ba42 fix: custom base path nullable (#3353) 2024-09-13 17:17:13 +00:00
Hassieb PakzadandGitHub 6ffec3337f feat(playground): add support for OpenAI o1 beta models (#3349) 2024-09-13 18:47:57 +02:00
steffen911 34a2a9cddf chore: remove pipeline concurrency limiter 2024-09-13 16:24:24 +02:00
Steffen SchmitzandGitHub 0854429850 feat: add env variables to toggle queue consumers (#3339) 2024-09-13 14:16:48 +00:00
Steffen SchmitzandGitHub 05fd690486 chore: deploy to staging ECS environment (#3327) 2024-09-13 13:49:27 +00:00
marliessophieandGitHub bc3f39272b style(evals): apply sticky headers to eval logs and eval config tables (#3347) 2024-09-13 13:27:22 +00:00
Marc KlingenandGitHub 96ace71f9a feat(self-host): support for custom base path via NEXT_PUBLIC_BASE_PATH (#3346) 2024-09-13 13:02:36 +00:00
marliessophieandGitHub 70415dcab5 fix: Time to first token filter in Generations table (#3345) 2024-09-13 11:53:33 +00:00
Hassieb PakzadandGitHub 6a18f249aa feat(cost-tracking): add OpenAI o1 models (#3343) 2024-09-13 12:00:10 +02:00
Marc KlingenandGitHub cc60d506a7 feat(cloud): improve upgrade ui (#3331)
* add link to billing settings to project settings

* add plan label to org switcher

* add upgrade menu item on free cloud plans
2024-09-12 16:49:49 +00:00
Steffen SchmitzandGitHub a31a7f238e chore: remove db query from healthcheck (#3330) 2024-09-12 17:24:40 +02:00
Marc KlingenandGitHub 41ebc77f86 feat(cloud): improve crisp integration (#3329)
* chore: update crisp sdk

* feat(cloud): improve crisp integration
2024-09-12 14:33:51 +00:00
marliessophieandGitHub cdc41cf573 chore(ui_markdown): whitelist images with valid ipv6 or ipv4 hostname (#3318) 2024-09-12 08:04:44 +00:00
Max DeichmannandGitHub c86e32f756 chore: improve client side sentry (#3312) 2024-09-12 09:19:07 +02:00
Max DeichmannandGitHub 85576c162a chore: downgrade codegen (#3319) 2024-09-11 21:58:05 +02:00
Max DeichmannandGitHub 93ad1e6880 perf: get observation I/O only if required (#3316) 2024-09-11 18:47:31 +00:00
marliessophieandGitHub 97b17fc8bb style(ui): fix hover effect on groups in column reordering (#3317) 2024-09-11 18:22:23 +00:00
marliessophieandGitHub 04e2d9249f fix(ui): populate default prompt data for chat prompts with custom role (#3295) 2024-09-11 12:36:59 +00:00
marliessophieandGitHub cb94a54f0c feat(ui): type to find column name in filter builder (#3290) 2024-09-11 08:31:18 +00:00
marliessophieandGitHub 7c90bbed0c fix(ui): persist dateRange filter state of dashboard (#3298) 2024-09-11 08:11:21 +00:00
Max DeichmannandGitHub f133526ba6 chore: create dependabot group for express (#3303)
fix
2024-09-11 09:24:45 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dc014de54c chore(deps-dev): bump kysely-codegen from 0.11.0 to 0.16.6 (#3301)
Bumps [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen) from 0.11.0 to 0.16.6.
- [Release notes](https://github.com/RobinBlomberg/kysely-codegen/releases)
- [Commits](https://github.com/RobinBlomberg/kysely-codegen/commits)

---
updated-dependencies:
- dependency-name: kysely-codegen
  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-09-11 09:12:04 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c6beec1ad5 chore(deps): bump @aws-sdk/lib-storage from 3.614.0 to 3.645.0 (#3235)
Bumps [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) from 3.614.0 to 3.645.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.645.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-09-10 23:17:43 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a810f7750f chore(deps): bump posthog-js from 1.154.6 to 1.161.3 (#3297)
Bumps [posthog-js](https://github.com/PostHog/posthog-js) from 1.154.6 to 1.161.3.
- [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.154.6...v1.161.3)

---
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-09-10 20:02:47 +02:00
marliessophieandGitHub 2b34c82e98 feat(ui): render 0 latency as such in trace timeline view (#3288) 2024-09-10 13:10:24 +00:00
marliessophieandGitHub fe283284ef feat(ui): move columns via drag and drop (#3266) 2024-09-10 12:40:59 +00:00
Steffen SchmitzandGitHub 307f3d18ce chore: add trace info to web logs and context to queue events (#3282) 2024-09-10 12:26:07 +00:00
Hassieb PakzadandGitHub 46fff7148e fix(ui/prompts): parse prompt variables correctly (#3286) 2024-09-10 12:05:52 +02:00
Steffen SchmitzandGitHub 9cce111be6 chore: add worker entrypoint to handle alternative database config (#3283) 2024-09-10 10:58:50 +02:00
Max DeichmannandGitHub dbd7f810c1 chore: remove sentry tunnel (#3274) 2024-09-09 17:22:53 +02:00
Max DeichmannandGitHub abc3ea2b5a chore: remove explicit profiling settings worker (#3272)
fix
2024-09-09 14:44:51 +00:00
Steffen SchmitzandGitHub 3d8a4f9d75 chore: downgrade winston instrumentation and fix worker import (#3268) 2024-09-09 13:35:05 +02:00
Max DeichmannandGitHub 7b4bef7c2e fix: revert async zod (#3264) 2024-09-07 09:24:59 +02:00
Marc KlingenandGitHub b10353115b fix(ui): x axis of timeseries charts on dashboard for some time ranges (#3261) 2024-09-06 20:30:58 +00:00
Marc KlingenandGitHub f3235ceccb fix(ui): overflow of long strings in sessions view (#3258) 2024-09-06 17:20:11 +00:00
Marc KlingenandGitHub 92455b4e5a fix(ui): reset feedback form after submit (#3257)
fix(ui): reset feedback form after successfully sending feedback
2024-09-06 16:49:49 +00:00
Max DeichmannandGitHub 217b92d548 perf: parse zod in ingestion pipeline async (#3253) 2024-09-06 16:10:23 +02:00
Marc KlingenandGitHub c6ddd3822f feat(users): add search to users table (#3251) 2024-09-06 01:14:31 +00:00
Marc KlingenandGitHub 67d6c32ec5 chore: Update README.md 2024-09-06 01:50:29 +02:00
Marc KlingenandGitHub d89379e693 chore: Update README.md 2024-09-06 01:48:47 +02:00
Marc KlingenandGitHub 4b8d3a764e chore: Update README.md 2024-09-06 01:26:09 +02:00
Marc Klingen 5757fa6e38 chore: update readme 2024-09-06 00:58:47 +02:00
Marc KlingenandGitHub 34c3e5af08 chore: Update README.md 2024-09-06 00:56:16 +02:00
Steffen SchmitzandGitHub c68ec2fca0 chore: move additional logs to debug level (#3250) 2024-09-05 18:44:18 +00:00
Max DeichmannandGitHub 03dda153e8 chore: remove log line (#3249) 2024-09-05 16:28:33 +00:00
Marc Klingen 63641521d8 chore: add langfuse.com and langfuse.dev to csp headers 2024-09-05 16:13:37 +02:00
Marc Klingen 29d11642c4 chore: update csp headers to include ph.langfuse.dev 2024-09-05 15:52:29 +02:00
Steffen SchmitzandGitHub fb98a404d2 chore: adjust log level for queue infos (#3248) 2024-09-05 14:14:56 +02:00
Max DeichmannandGitHub 032fce56c4 chore: upgrade fern (#3247) 2024-09-05 11:30:10 +00:00
Max DeichmannandGitHub 518b8df2da fix: also track langfuse SDK req headers with underscores on span (#2972) 2024-09-05 11:00:13 +00:00
Max DeichmannandGitHub 70761f7ab0 chore: improve log level (#3244) 2024-09-05 09:01:22 +00:00
Max DeichmannandGitHub a85d4ccf07 chore: improve zod monitoring in ingestion pipeline (#3239) 2024-09-05 08:47:17 +00:00
Steffen SchmitzandGitHub 1b42eae7dd chore: move createAuthedApiRoute logging into metadata (#3238) 2024-09-05 09:09:07 +02:00
Marc Klingen 898cca56f9 chore: release v2.78.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-09-04 17:29:47 +02:00
Steffen SchmitzandGitHub 83e052e769 feat: migrate web api to new logger (#3230) 2024-09-04 17:02:26 +02:00
Steffen SchmitzandGitHub b5f19771fa feat: migrate shared package to new logger (#3229) 2024-09-04 16:43:36 +02:00
Marc KlingenandGitHub 644390183b fix: rename LANGFUSE_PROVISION_ to LANGFUSE_INIT_ ahead of changelog release (#3228) 2024-09-04 15:06:47 +02:00
Steffen SchmitzandGitHub 4d8e9003fe feat: add structured logging option (#3221) 2024-09-04 14:53:42 +02:00
Marc KlingenandGitHub 651c46a270 docs: add provisioning envs to .env.prod.example (#3227) 2024-09-04 14:24:53 +02:00
Max DeichmannandGitHub 6b127bced0 chore: add instrumentation to ingestion zod parsing (#3224) 2024-09-04 10:08:05 +00:00
Steffen SchmitzandGitHub e3a837b561 feat: add new /ready endpoint to separate liveness from readiness (#3222) 2024-09-04 11:53:41 +02:00
steffen911 11c04f02c8 chore: release v2.77.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-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-09-04 10:50:09 +02:00
Steffen SchmitzandGitHub 536aa138bc fix: reconnect workers to redis on errors (#3211) 2024-09-04 10:22:59 +02:00
Marc KlingenandGitHub 61b596c46a fix(docker): prevent web build oom by allocating more memory to build step in Dockerfile (#3217) 2024-09-04 00:18:15 +02:00
Marc KlingenandGitHub ddf43c0836 feat(self-host): provision instance with default org/project/user/keys via LANGFUSE_PROVISION_* envs (#3216) 2024-09-03 23:50:20 +02:00
Marc KlingenandGitHub d5ab03892a chore(ee): add EE to VersionLabel (#3214) 2024-09-03 14:03:57 +00:00
918b6643d2 chore(release): nextjs client side refresh after redeployment (#3116)
Co-authored-by: Marc Klingen <git@marcklingen.com>
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-09-03 12:42:57 +02:00
marliessophieandGitHub 937b4c0f0f feat(ui): deletion of dataset runs (#3209) 2024-09-03 12:26:02 +02:00
318 changed files with 14521 additions and 8147 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
[codespell]
skip = .git,*.pdf,*.svg,package-lock.json,*.prisma,pnpm-lock.yaml
ignore-words-list = afterall,vertx
ignore-words-list = afterall,vertx,notIn
+22 -5
View File
@@ -34,11 +34,25 @@ EMAIL_FROM_ADDRESS="" # Defines the email address to use as the from address.
SMTP_CONNECTION_URL="" # Defines the connection url for smtp server.
# S3 storage
S3_ENDPOINT=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET_NAME=
S3_REGION=
S3_ENDPOINT=http://localhost:9090
S3_ACCESS_KEY_ID=minio
S3_SECRET_ACCESS_KEY=miniosecret
S3_BUCKET_NAME=mybucket
S3_REGION=us-east-1
## Necessary for minio compatibility
S3_FORCE_PATH_STYLE=true
# S3 Event Bucket Upload
## Set to true to test uploading all events to S3
LANGFUSE_S3_EVENT_UPLOAD_ENABLED=false
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=mybucket
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=miniosecret
LANGFUSE_S3_EVENT_UPLOAD_REGION=us-east-1
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://localhost:9090
## Necessary for minio compatibility
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
# Set during docker build of application
# Used to disable environment verification at build time
@@ -51,3 +65,6 @@ REDIS_AUTH="myredissecret"
LANGFUSE_WORKER_PASSWORD=mybasicauthsecret
# openssl rand -hex 32 used only here
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# speeds up local development by not executing init scripts on server startup
NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT="false"
+44 -2
View File
@@ -28,17 +28,28 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# Use CSP headers to enforce HTTPS, optional
# LANGFUSE_CSP_ENFORCE_HTTPS="true"
# Configure base path for self-hosting, optional
# Note: You need to build the docker image with the base path set and cannot use the pre-built docker image if you set this.
# NEXT_PUBLIC_BASE_PATH="/app"
# Docker only, optional
# PORT=3000
# HOSTNAME=localhost
# Opentelemetry, optional
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
OTEL_SERVICE_NAME="langfuse"
# Default role for users who sign up, optional, can be org or org+project
# LANGFUSE_DEFAULT_ORG_ID=
# LANGFUSE_DEFAULT_ORG_ROLE=
# LANGFUSE_DEFAULT_PROJECT_ID=
# LANGFUSE_DEFAULT_PROJECT_ROLE=
# Logging, optional
# LANGFUSE_LOG_LEVEL=info
# LANGFUSE_LOG_FORMAT=text
# Enable experimental features, optional
# LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=true
@@ -56,6 +67,10 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# AUTH_GITHUB_CLIENT_ID=
# AUTH_GITHUB_CLIENT_SECRET=
# AUTH_GITHUB_ALLOW_ACCOUNT_LINKING=false
# AUTH_GITLAB_CLIENT_ID=
# AUTH_GITLAB_CLIENT_SECRET=
# AUTH_GITLAB_ALLOW_ACCOUNT_LINKING=false
# AUTH_GITLAB_ISSUER=
# AUTH_AZURE_AD_CLIENT_ID=
# AUTH_AZURE_AD_CLIENT_SECRET=
# AUTH_AZURE_AD_TENANT_ID=
@@ -93,10 +108,33 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# S3_REGION=
# BATCH_EXPORT_DOWNLOAD_LINK_EXPIRATION_HOURS=
# S3 storage for events, optional, used to persist all incoming events
# LANGFUSE_S3_EVENT_UPLOAD_ENABLED="true"
# LANGFUSE_S3_EVENT_UPLOAD_BUCKET=
# Optional prefix to be used within the bucket. Must end with `/` if set
# LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
# The following four options are optional and fallback to the normal SDK credential provider chain if omitted
# See https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
# LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=
# LANGFUSE_S3_EVENT_UPLOAD_REGION=
# LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=
# LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=
# Exports are streamed to S3 in pages to avoid memory issues
# The page size can be adjusted if needed to optimize performance
# DB_EXPORT_PAGE_SIZE=1000
# Automated provisioning of default resources
# LANGFUSE_INIT_ORG_ID=org-id
# LANGFUSE_INIT_ORG_NAME=org-name
# LANGFUSE_INIT_PROJECT_ID=project-id
# LANGFUSE_INIT_PROJECT_NAME=project-name
# LANGFUSE_INIT_PROJECT_PUBLIC_KEY=pk-1234567890
# LANGFUSE_INIT_PROJECT_SECRET_KEY=sk-1234567890
# LANGFUSE_INIT_USER_EMAIL=user@example.com
# LANGFUSE_INIT_USER_NAME=User Name
# LANGFUSE_INIT_USER_PASSWORD=password
### START Enterprise Edition Configuration
@@ -109,6 +147,12 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# LANGFUSE_UI_DOCUMENTATION_HREF=https://docs.example.com
# LANGFUSE_UI_SUPPORT_HREF=https://support.example.com
# LANGFUSE_UI_FEEDBACK_HREF=https://feedback.example.com
# LANGFUSE_UI_LOGO_LIGHT_MODE_HREF=https://static.langfuse.com/langfuse-dev/example-logo-light-mode.png
# LANGFUSE_UI_LOGO_DARK_MODE_HREF=https://static.langfuse.com/langfuse-dev/example-logo-dark-mode.png
# LANGFUSE_UI_DEFAULT_MODEL_ADAPTER=Anthropic # OpenAI, Anthropic, Azure
# LANGFUSE_UI_DEFAULT_BASE_URL_OPENAI=https://api.openai.com/v1
# LANGFUSE_UI_DEFAULT_BASE_URL_ANTHROPIC=https://api.anthropic.com
# LANGFUSE_UI_DEFAULT_BASE_URL_AZURE_OPENAI=https://{instanceName}.openai.azure.com/openai/deployments
### END Enterprise Edition Configuration
@@ -145,7 +189,6 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# NEXT_SENTRY_PROJECT=
# SENTRY_AUTH_TOKEN=
# SENTRY_CSP_REPORT_URI=
# LANGFUSE_WORKER_BETTERSTACK_TOKEN=
# Cloudflare Turnstile
@@ -201,7 +244,6 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_BATCH_SIZE=
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=
# LANGFUSE_INGESTION_CLICKHOUSE_MAX_ATTEMPTS=
# LANGFUSE_LOG_LEVEL=
# LANGFUSE_LEGACY_INGESTION_WORKER_CONCURRENCY=
# LANGFUSE_ASYNC_INGESTION_PROCESSING="true"
# QUEUE_CONSUMER_LEGACY_INGESTION_QUEUE_IS_ENABLED="true"
+4
View File
@@ -32,6 +32,10 @@ updates:
patterns:
- "lodash"
- "@types/lodash"
express:
patterns:
- "express"
- "@types/express"
observability:
patterns:
- "dd-trace"
+75
View File
@@ -0,0 +1,75 @@
name: WorkflowCall - Deploy to ECS Service
on:
workflow_call:
inputs:
environment:
type: string
description: Deployment environment
required: true
service:
type: string
description: Name of the service to be deployed, e.g. web-ingestion, web, or worker.
required: true
jobs:
ecs-deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Get app name
uses: winterjung/split@v2
id: split
with:
msg: ${{ inputs.service }}
separator: "-"
- name: Checkout code
uses: actions/checkout@v4
- name: Authenticate with AWS
# GitHub/AWS recommend to use OIDC here: https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#oidc
# Probably more painful to configure, but would remove all long-lived credentials.
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- name: Login to AWS ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push Docker image
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
REPOSITORY: ${{ steps.split.outputs._0 }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build \
-t $REGISTRY/$REPOSITORY:$IMAGE_TAG \
-f ./${{ steps.split.outputs._0 }}/Dockerfile \
--build-arg NEXT_PUBLIC_LANGFUSE_CLOUD_REGION=${{ vars.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION }} \
--build-arg NEXT_LANGFUSE_TRACING_SAMPLE_RATE=${{ vars.NEXT_LANGFUSE_TRACING_SAMPLE_RATE }} \
--build-arg NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }} \
--build-arg NEXT_PUBLIC_DEMO_ORG_ID=${{ vars.NEXT_PUBLIC_DEMO_ORG_ID }} \
--build-arg NEXT_PUBLIC_DEMO_PROJECT_ID=${{ vars.NEXT_PUBLIC_DEMO_PROJECT_ID }} \
--build-arg NEXT_PUBLIC_SENTRY_DSN=${{ vars.NEXT_PUBLIC_SENTRY_DSN }} \
--build-arg NEXT_PUBLIC_BUILD_ID=${{ github.sha }} \
--build-arg NEXT_PUBLIC_POSTHOG_KEY=${{ vars.NEXT_PUBLIC_POSTHOG_KEY }} \
--build-arg NEXT_PUBLIC_POSTHOG_HOST=${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} \
--build-arg NEXT_PUBLIC_CRISP_WEBSITE_ID=${{ vars.NEXT_PUBLIC_CRISP_WEBSITE_ID }} \
--build-arg SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \
--build-arg SENTRY_ORG=${{ vars.SENTRY_ORG }} \
--build-arg SENTRY_PROJECT=${{ vars.SENTRY_PROJECT }} \
.
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
- name: Render AWS ECS Task Definition
id: render-task-definition
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
container-name: ${{ inputs.service }}
image: ${{ steps.login-ecr.outputs.registry }}/${{ steps.split.outputs._0 }}:${{ github.sha }}
task-definition-family: ${{ inputs.environment }}-${{ inputs.service }}
- name: Update AWS ECS Service
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-task-definition.outputs.task-definition }}
service: ${{ inputs.environment }}-${{ inputs.service }}
cluster: ${{ inputs.environment }}-cluster
wait-for-service-stability: true
+2 -2
View File
@@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Start containers
run: docker compose -f "docker-compose.yml" up -d --build
@@ -31,7 +31,7 @@ jobs:
uses: pnpm/action-setup@v2.2.4
- name: Setup Node 18
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
+92
View File
@@ -0,0 +1,92 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: ["main", "production", "v*"]
pull_request:
branches: ["main", "production", "v*"]
schedule:
- cron: "31 13 * * 0"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
+105
View File
@@ -0,0 +1,105 @@
on:
push:
branches:
- main
- production
workflow_dispatch:
inputs:
service:
description: 'Service to be deployed'
type: choice
options:
- all
- web
- web-ingestion
- worker
required: true
environment:
description: 'Environment to deploy to'
type: choice
options:
- staging
- prod-eu-temp
- prod-us
required: true
concurrency:
# Support concurrent `push` and `workflow_dispatch`` actions
group: deploy-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
name: Deploy to ECS
jobs:
affected-services:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.affected-services.outputs.result }}
steps:
- name: Get affected services
id: affected-services
uses: actions/github-script@v7
with:
script: |
if (context.eventName === "workflow_dispatch") {
if (context.payload.inputs.service === "all") {
return `["web", "web-ingestion", "worker"]`
}
return `["${context.payload.inputs.service}"]`
}
if (context.eventName === "push") {
return `["web", "web-ingestion", "worker"]`
}
return "[]"
result-encoding: string
- name: Print services to build
uses: actions/github-script@v7
env:
services: ${{ steps.affected-services.outputs.result }}
with:
result-encoding: string
script: |
console.log('Services', `${process.env.services}` ?? 'n/a');
affected-environments:
runs-on: ubuntu-latest
outputs:
environments: ${{ steps.affected-environments.outputs.result }}
steps:
- name: Get affected environments
id: affected-environments
uses: actions/github-script@v7
with:
script: |
if (context.eventName === "workflow_dispatch") {
return `["${context.payload.inputs.environment}"]`
}
if (context.eventName === "push") {
if (context.ref === "refs/heads/main") {
return `["staging"]`
}
if (context.ref === "refs/heads/production") {
return `["prod-eu-temp", "prod-us"]`
}
}
return "[]"
result-encoding: string
- name: Print environments to build
uses: actions/github-script@v7
env:
environments: ${{ steps.affected-environments.outputs.result }}
with:
result-encoding: string
script: |
console.log('Environments', `${process.env.environments}` ?? 'n/a');
ecs-deploy:
uses: ./.github/workflows/_deploy_ecs_service.yml
needs: [affected-services, affected-environments]
secrets: inherit
strategy:
matrix:
service: ${{ fromJson(needs.affected-services.outputs.services) }}
environment: ${{ fromJson(needs.affected-environments.outputs.environments) }}
with:
service: ${{ matrix.service }}
environment: ${{ matrix.environment }}
+2 -2
View File
@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18
+39 -27
View File
@@ -16,11 +16,11 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
@@ -35,24 +35,20 @@ jobs:
run: pnpm run lint
test-docker-build:
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set NEXT_PUBLIC_BUILD_ID
run: echo "NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Build and run both images from compose
run: |
docker compose -f docker-compose.build.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
- name: Check server health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
- name: Check worker health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3030/api/health; do sleep 2; done'
- name: Ensure no unhealthy status
run: |
if docker-compose ps | grep "(unhealthy)"; then
@@ -62,6 +58,14 @@ jobs:
echo "All services are healthy"
fi
- name: Check worker health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3030/api/health; do sleep 2; done'
- name: Check server health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
tests-web:
timeout-minutes: 20
runs-on: ubuntu-latest
@@ -76,12 +80,12 @@ jobs:
with:
swap-size-gb: 10
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
@@ -94,7 +98,7 @@ jobs:
- name: Load default env
run: |
cp .env.dev.example .env
grep -v -e '^REDIS_HOST=' .env.dev.example > .env
grep -v -e '^S3_BUCKET_NAME=' -e '^REDIS_HOST=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev.example > .env
- name: Run + migrate
run: |
@@ -107,17 +111,22 @@ jobs:
- name: Seed DB
run: |
pnpm run db:migrate
pnpm run db:seed
- name: Build
run: pnpm run build
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
- name: Start Langfuse
run: (pnpm run start&)
env:
LANGFUSE_INIT_ORG_ID: "seed-org-id"
LANGFUSE_INIT_ORG_NAME: "Seed Org"
LANGFUSE_INIT_PROJECT_ID: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"
LANGFUSE_INIT_PROJECT_NAME: "Seed Project"
LANGFUSE_INIT_PROJECT_PUBLIC_KEY: "pk-lf-1234567890"
LANGFUSE_INIT_PROJECT_SECRET_KEY: "sk-lf-1234567890"
LANGFUSE_INIT_USER_EMAIL: "demo@langfuse.com"
LANGFUSE_INIT_USER_NAME: "Demo User"
LANGFUSE_INIT_USER_PASSWORD: "password"
- name: run tests
run: pnpm --filter=web run test
@@ -136,13 +145,13 @@ jobs:
with:
swap-size-gb: 10
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
@@ -193,11 +202,11 @@ jobs:
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
@@ -235,11 +244,11 @@ jobs:
e2e-server-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
@@ -313,13 +322,16 @@ jobs:
with:
version: 9.5.0
- name: Setup node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20
cache-dependency-path: "pnpm-lock.yaml"
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set NEXT_PUBLIC_BUILD_ID
run: echo "NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Log in to the GitHub Container registry
uses: docker/login-action@v2
@@ -1,29 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to web
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4060"
PORTER_DEPLOYMENT_TARGET_ID: 3be075c0-b121-4123-aec1-5dc571626b0c
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: web
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4060 }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- main
name: Deploy to web (Staging)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4037"
PORTER_DEPLOYMENT_TARGET_ID: c22d8fa2-f97c-4e1f-9cae-83cc6bfbad35
PORTER_HOST: https://dashboard.getporter.dev
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: web
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4037 }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to web (US temp)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4054"
PORTER_DEPLOYMENT_TARGET_ID: cddc0f2a-bb19-401a-9819-29f59f76b101
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: web
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4054 }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to worker (EU temp)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4060"
PORTER_DEPLOYMENT_TARGET_ID: 3be075c0-b121-4123-aec1-5dc571626b0c
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: worker
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4060 }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- main
name: Deploy to worker (staging)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4037"
PORTER_DEPLOYMENT_TARGET_ID: c22d8fa2-f97c-4e1f-9cae-83cc6bfbad35
PORTER_HOST: https://dashboard.getporter.dev
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: worker
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4037 }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to worker (US temp)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4054"
PORTER_DEPLOYMENT_TARGET_ID: cddc0f2a-bb19-401a-9819-29f59f76b101
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: worker
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4054 }}
+2 -2
View File
@@ -2,10 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"name": "Web and Worker: server side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev"
"command": "pnpm run dev"
},
{
"name": "Next.js: debug client-side",
+29 -30
View File
@@ -1,8 +1,7 @@
![langfuse_logo_1024](https://github.com/user-attachments/assets/f17d8fc1-8206-4afa-a5ec-774826be88a8)
![Langfuse GitHub Banner](https://github.com/langfuse/langfuse/assets/121163007/6035f0f3-d691-4963-b5d0-10cf506e9d42)
<div align="center"><h1> Langfuse: Open Source LLM Observability & Engineering Platform</h4></div>
<div align="center"><h2> Debug and improve your LLM app</h4></div>
<div align="center"><h3> LLM Observability, Prompt Management, LLM Evaluations, Datasets, LLM Metrics and Prompt Playground</h4></div>
<div align="center"><h1>Langfuse: Open Source LLM Engineering Platform</h1></div>
<div align="center"><h4>LLM Observability, Prompt Management, LLM Evaluations,<br/>Datasets, LLM Metrics, and Prompt Playground</h4></div>
<div align="center">
<div>
@@ -28,15 +27,15 @@
</div>
<span>Langfuse uses <a href="https://github.com/orgs/langfuse/discussions"><strong>Github Discussions</strong></a> for Support and Feature Requests.</span>
<br/>
<span>We're hiring. <a href="https://langfuse.com/careers"><strong>Join us</strong></a> in Backend Engineering, Product Engineering, and Developer Relations.</span>
<span>We're hiring. <a href="https://langfuse.com/careers"><strong>Join us</strong></a> in Product Engineering and Developer Relations.</span>
<br/>
<br/>
<div>
<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>
<a href="https://pypi.python.org/pypi/langfuse"><img src="https://img.shields.io/pypi/v/langfuse.svg?style=flat-square&label=pypi+langfuse" alt="langfuse Python package on PyPi"></a>
<a href="https://pypi.python.org/pypi/langfuse"><img src="https://img.shields.io/pypi/dm/langfuse?style=flat-square&logo=python&logoColor=white&label=pypi%20langfuse&color=blue" alt="langfuse Python package on PyPi"></a>
<a href="https://www.npmjs.com/package/langfuse"><img src="https://img.shields.io/npm/dm/langfuse?style=flat-square&logo=npm&logoColor=white&label=npm%20langfuse&color=blue" alt="langfuse npm package"></a>
</div>
</div>
</br>
@@ -93,10 +92,10 @@ docker compose up -d
### Self-host (docker)
Langfuse is simple to self-host and keep updated. It currently requires only a single docker container.
Langfuse is simple to self-host and keep updated. It currently requires only a single docker container and a postgres database.
[→ Self Hosting Instructions](https://langfuse.com/docs/deployment/self-host)
Templated deployments: [Railway, GCP Cloud Run, AWS Fargate, Kubernetes and others](https://langfuse.com/docs/deployment/self-host#platform-specific-information)
Templated deployments: [Railway, GCP, AWS, Azure, Kubernetes and others](https://langfuse.com/docs/deployment/self-host#platform-specific-information)
## Get Started
@@ -108,31 +107,31 @@ You need a Langfuse public and secret key to get started. Sign up [here](https:/
Note: We recommend using our fully async, typed [SDKs](https://langfuse.com/docs/sdk) that allow you to instrument any LLM application with any underlying model. They are available in [Python (Decorators)](https://langfuse.com/docs/sdk/python) & [JS/TS](https://langfuse.com/docs/sdk/typescript). The SDKs will always be the most fully featured and stable way to ingest data into Langfuse.
You may want to use another integration to get started quickly or implement a use case that we do not yet support. However, we recommend to migrate to the Langfuse SDKs over time to ensure performance and stability.
See the [→ Quickstart](https://langfuse.com/docs/get-started) to integrate Langfuse.
### LLM Observability Integrations
| Integration | Supports | Description |
| -------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [SDK](/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI](/docs/integrations/openai) | Python, JS/TS | Automated instrumentation using drop-in replacement of OpenAI SDK. |
| [Langchain](/docs/integrations/langchain) | Python, JS/TS | Automated instrumentation by passing callback handler to Langchain application. |
| [LlamaIndex](/docs/integrations/llama-index/get-started) | Python | Automated instrumentation via LlamaIndex callback system. |
| [Haystack](/docs/integrations/haystack) | Python | Automated instrumentation via Haystack content tracing system. |
| [LiteLLM](/docs/integrations/litellm) | Python, JS/TS (proxy only) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
| [API](/docs/api) | | Directly call the public API. OpenAPI spec available. |
| Integration | Supports | Description |
| ---------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [SDK](https://langfuse.com/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI](https://langfuse.com/docs/integrations/openai) | Python, JS/TS | Automated instrumentation using drop-in replacement of OpenAI SDK. |
| [Langchain](https://langfuse.com/docs/integrations/langchain) | Python, JS/TS | Automated instrumentation by passing callback handler to Langchain application. |
| [LlamaIndex](https://langfuse.com/docs/integrations/llama-index/get-started) | Python | Automated instrumentation via LlamaIndex callback system. |
| [Haystack](https://langfuse.com/docs/integrations/haystack) | Python | Automated instrumentation via Haystack content tracing system. |
| [LiteLLM](https://langfuse.com/docs/integrations/litellm) | Python, JS/TS (proxy only) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
| [Vercel AI SDK](https://langfuse.com/docs/integrations/vercel-ai-sdk) | JS/TS | TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js. |
| [API](https://langfuse.com/docs/api) | | Directly call the public API. OpenAPI spec available. |
Packages that integrate with Langfuse:
Packages integrated with Langfuse:
| Name | Description |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [Instructor](/docs/integrations/instructor) | Library to get structured LLM outputs (JSON, Pydantic) |
| [Mirascope](/docs/integrations/mirascope) | Python toolkit for building LLM applications. |
| [AI SDK by Vercel](/docs/sdk/typescript/example-vercel-ai) | Typescript SDK that makes streaming LLM outputs super easy. |
| [Flowise](/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
| [Langflow](/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
| Name | Description |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [Instructor](https://langfuse.com/docs/integrations/instructor) | Library to get structured LLM outputs (JSON, Pydantic) |
| [Dify](https://langfuse.com/docs/integrations/dify) | Open source LLM app development platform with no-code builder. |
| [Ollama](https://langfuse.com/docs/integrations/ollama) | Easily run open source LLMs on your own machine. |
| [Mirascope](https://langfuse.com/docs/integrations/mirascope) | Python toolkit for building LLM applications. |
| [Flowise](https://langfuse.com/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
| [Langflow](https://langfuse.com/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
## Questions and feedback
@@ -140,13 +139,13 @@ Packages that integrate with Langfuse:
- [Roadmap](https://langfuse.com/roadmap)
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions)
- [Feature Requests](https://langfuse.com/idea
- [Feature Requests](https://langfuse.com/ideas)
### Support and feedback
In order of preference the best way to communicate with us:
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions): Contribute [ideas](https://langfuse.com/idea) [support requests](https://github.com/orgs/langfuse/discussions/categories/support) and [report bugs](https://github.com/langfuse/langfuse/issues/new?labels=%F0%9F%90%9E%E2%9D%94+unconfirmed+bug&projects=&template=bug_report.yml&title=bug%3A+) (preferred as we create a permanent, indexed artifact for other community members)
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions) (preferred): Contribute [ideas](https://langfuse.com/ideas), [support requests](https://langfuse.com/gh-support) and [report bugs](https://langfuse.com/issues)
- [Discord](https://langfuse.com/discord): community support
- Privately: contact at langfuse dot com
+9
View File
@@ -22,6 +22,15 @@ services:
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
- LANGFUSE_WORKER_HOST=${LANGFUSE_WORKER_HOST:-worker}
- LANGFUSE_WORKER_PASSWORD=${LANGFUSE_WORKER_PASSWORD:-mybasicauthsecret}
- LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-}
- LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-}
- LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-}
- LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-}
- LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
- LANGFUSE_INIT_PROJECT_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
- LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-}
- LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-}
- LANGFUSE_INIT_USER_PASSWORD=${LANGFUSE_INIT_USER_PASSWORD:-}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/public/health"]
+34
View File
@@ -17,6 +17,38 @@ services:
depends_on:
- postgres
minio:
image: minio/minio
container_name: minio
command: server /data --console-address ":9001"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: miniosecret
ports:
- "9090:9000"
- "9091:9001"
volumes:
- langfuse_minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
miniocreatebucket:
image: minio/mc
container_name: miniocreatebucket
entrypoint: ["/bin/sh", "-c"]
command: >
"mc alias set minio http://minio:9000 minio miniosecret &&
mc rm -r --force minio/mybucket || true &&
mc mb minio/mybucket &&
mc policy set download minio/mybucket"
depends_on:
minio:
condition: service_healthy
redis:
image: redis:7.2.4
restart: always
@@ -45,3 +77,5 @@ volumes:
driver: local
langfuse_clickhouse_logs:
driver: local
langfuse_minio_data:
driver: local
+9
View File
@@ -14,6 +14,15 @@ services:
- NEXTAUTH_URL=http://localhost:3000
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true}
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
- LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-}
- LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-}
- LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-}
- LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-}
- LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
- LANGFUSE_INIT_PROJECT_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
- LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-}
- LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-}
- LANGFUSE_INIT_USER_PASSWORD=${LANGFUSE_INIT_USER_PASSWORD:-}
db:
image: postgres
+4 -3
View File
@@ -25,8 +25,9 @@
},
"dependencies": {
"@langfuse/shared": "workspace:*",
"@opentelemetry/api": ">=1.0.0 <1.9.0",
"next": "^14.2.6",
"@opentelemetry/api": ">=1.0.0 <1.10.0",
"axios": "^1.7.7",
"next": "^14.2.15",
"next-auth": "^4.24.7",
"zod": "^3.23.8"
},
@@ -39,7 +40,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5",
"prettier": "^3.3.3",
"ts-node": "^10.9.2",
"tsc-watch": "^6.2.0",
"typescript": "^5.4.5"
+5 -2
View File
@@ -1,9 +1,12 @@
import { z } from "zod";
import { env as sharedEnv } from "@langfuse/shared";
import { env as sharedEnv, removeEmptyEnvVariables } from "@langfuse/shared";
const EnvSchema = z.object({
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION: z.string().optional(),
LANGFUSE_EE_LICENSE_KEY: z.string().optional(),
});
export const env = { ...sharedEnv, ...EnvSchema.parse(process.env) };
export const env = {
...sharedEnv,
...EnvSchema.parse(removeEmptyEnvVariables(process.env)),
};
+2 -2
View File
@@ -31,10 +31,10 @@ service:
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)
docs: Optional filter to only include traces and observations 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)
docs: Optional filter to only include traces and observations before a certain datetime (ISO 8601)
response: DailyMetrics
types:
DailyMetrics:
+3 -13
View File
@@ -3,32 +3,22 @@ groups:
local:
generators:
- name: fernapi/fern-openapi
version: 0.0.28
version: 0.0.31
output:
location: local-file-system
path: ../../../web/public/generated/api
config:
namespaceExport: Langfuse
allowCustomFetcher: true
- name: fernapi/fern-python-sdk
version: 1.1.0-rc2
version: 2.16.0
output:
location: local-file-system
path: ../../../generated/python
config:
client_class_name: FernLangfuse
improved_imports: false
inline_request_params: false
pydantic_config:
require_optional_fields: false
use_str_enums: false
- name: fernapi/fern-typescript-node-sdk
version: 0.7.1
output:
location: local-file-system
path: ../../../generated/typescript-server
config:
namespaceExport: Langfuse
allowCustomFetcher: true
- name: fernapi/fern-postman
version: 0.0.45
output:
+1 -1
View File
@@ -1,4 +1,4 @@
{
"organization": "finto",
"version": "0.30.3"
"version": "0.43.7"
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "langfuse",
"version": "2.76.0",
"version": "2.84.0",
"author": "engineering@langfuse.com",
"license": "MIT",
"private": true,
@@ -33,7 +33,7 @@
"braces": "3.0.3",
"dotenv-cli": "^7.4.2",
"husky": "^9.0.11",
"prettier": "^3.2.5",
"prettier": "^3.3.3",
"release-it": "^17.3.0",
"turbo": "^1.13.4"
},
+1 -1
View File
@@ -11,7 +11,7 @@
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.12.0",
"@vercel/style-guide": "^6.0.0",
"eslint-config-next": "^14.2.6",
"eslint-config-next": "^14.2.14",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "^1.13.4",
"eslint-plugin-only-warn": "^1.1.0",
+25 -19
View File
@@ -21,6 +21,10 @@
"import": "./dist/src/server/index.js",
"require": "./dist/src/server/index.js"
},
"./src/server/auth/apiKeys": {
"import": "./dist/src/server/auth/apiKeys.js",
"require": "./dist/src/server/auth/apiKeys.js"
},
"./encryption": {
"import": "./dist/src/encryption/index.js",
"require": "./dist/src/encryption/index.js"
@@ -51,59 +55,61 @@
"dependencies": {
"@anthropic-ai/tokenizer": "^0.0.4",
"@aws-sdk/client-s3": "^3.627.0",
"@aws-sdk/lib-storage": "^3.568.0",
"@aws-sdk/lib-storage": "^3.667.0",
"@aws-sdk/s3-request-presigner": "^3.554.0",
"@clickhouse/client": "^1.4.0",
"@langchain/anthropic": "^0.1.21",
"@langchain/core": "^0.1.61",
"@langchain/openai": "^0.0.33",
"@opentelemetry/api": ">=1.0.0 <1.9.0",
"@prisma/client": "^5.17.0",
"@langchain/anthropic": "^0.3.1",
"@langchain/core": "^0.3.7",
"@langchain/openai": "^0.3.0",
"@opentelemetry/api": ">=1.0.0 <1.10.0",
"@prisma/client": "^5.20.0",
"@react-email/components": "^0.0.19",
"@react-email/render": "^0.0.15",
"@types/bcryptjs": "^2.4.6",
"axios": "^1.7.7",
"bcryptjs": "^2.4.3",
"bullmq": "^5.12.10",
"dd-trace": "^5.19.0",
"dd-trace": "^5.23.1",
"decimal.js": "^10.4.3",
"exponential-backoff": "^3.1.1",
"ioredis": "^5.4.1",
"kysely": "^0.27.4",
"langchain": "^0.2.6",
"langchain": "^0.3.2",
"lodash": "^4.17.21",
"next-auth": "^4.24.7",
"nodemailer": "^6.9.13",
"nodemailer": "^6.9.15",
"prisma-extension-kysely": "^2.1.0",
"uuid": "^9.0.1",
"winston": "^3.14.2",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.2"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@types/lodash": "^4.17.7",
"@types/lodash": "^4.17.10",
"@types/node": "^20.11.29",
"@types/nodemailer": "^6.4.15",
"@types/pg": "^8.11.6",
"@types/nodemailer": "^6.4.16",
"@types/pg": "^8.11.10",
"@types/uuid": "^9.0.8",
"@typescript-eslint/parser": "^7.12.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-prettier": "^5.1.3",
"kysely-codegen": "^0.11.0",
"nodemon": "^3.1.3",
"prettier": "^3.2.5",
"prisma": "^5.17.0",
"kysely-codegen": "^0.16.8",
"nodemon": "^3.1.7",
"prettier": "^3.3.3",
"prisma": "^5.20.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.5.3"
"vitest": "^2.1.2"
},
"peerDependencies": {
"@types/react": "^18.2.79",
"react": "^18.0.0"
"@types/react": "~18.2.79",
"react": "~18.2.0"
}
}
+38
View File
@@ -37,6 +37,16 @@ export const ScoreDataType = {
BOOLEAN: "BOOLEAN"
} as const;
export type ScoreDataType = (typeof ScoreDataType)[keyof typeof ScoreDataType];
export const AnnotationQueueStatus = {
PENDING: "PENDING",
COMPLETED: "COMPLETED"
} as const;
export type AnnotationQueueStatus = (typeof AnnotationQueueStatus)[keyof typeof AnnotationQueueStatus];
export const AnnotationQueueObjectType = {
TRACE: "TRACE",
OBSERVATION: "OBSERVATION"
} as const;
export type AnnotationQueueObjectType = (typeof AnnotationQueueObjectType)[keyof typeof AnnotationQueueObjectType];
export const DatasetStatus = {
ACTIVE: "ACTIVE",
ARCHIVED: "ARCHIVED"
@@ -80,6 +90,31 @@ export type Account = {
scope: string | null;
id_token: string | null;
session_state: string | null;
refresh_token_expires_in: number | null;
created_at: number | null;
};
export type AnnotationQueue = {
id: string;
name: string;
description: string | null;
score_config_ids: Generated<string[]>;
project_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
};
export type AnnotationQueueItem = {
id: string;
queue_id: string;
object_id: string;
object_type: AnnotationQueueObjectType;
status: Generated<AnnotationQueueStatus>;
locked_at: Timestamp | null;
locked_by_user_id: string | null;
annotator_user_id: string | null;
completed_at: Timestamp | null;
project_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
};
export type ApiKey = {
id: string;
@@ -409,6 +444,7 @@ export type Score = {
observation_id: string | null;
config_id: string | null;
string_value: string | null;
queue_id: string | null;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
data_type: Generated<ScoreDataType>;
@@ -505,6 +541,8 @@ export type VerificationToken = {
};
export type DB = {
Account: Account;
annotation_queue_items: AnnotationQueueItem;
annotation_queues: AnnotationQueue;
api_keys: ApiKey;
audit_logs: AuditLog;
batch_exports: BatchExport;
@@ -0,0 +1,30 @@
-- For OpenAI reasoning models, manually tokenizing output string does not account for reasoning tokens, thus would lead to incorrect cost
-- So no manual tokenizer config is provided
-- Reference: https://platform.openai.com/docs/guides/reasoning/how-reasoning-works
INSERT INTO models (
id,
project_id,
model_name,
match_pattern,
start_date,
input_price,
output_price,
total_price,
unit,
tokenizer_id,
tokenizer_config
)
VALUES
-- o1-preview
('cm10ivcdp0000gix7lelmbw80', NULL, 'o1-preview', '(?i)^(o1-preview)$', NULL, 0.000015, 0.00006, NULL, 'TOKENS', NULL, NULL),
-- o1-preview-2024-09-12
('cm10ivo130000n8x7qopcjjcg', NULL, 'o1-preview-2024-09-12', '(?i)^(o1-preview-2024-09-12)$', NULL, 0.000015, 0.00006, NULL, 'TOKENS', NULL, NULL),
-- o1-mini
('cm10ivwo40000r1x7gg3syjq0', NULL, 'o1-mini', '(?i)^(o1-mini)$', NULL, 0.000003, 0.000012, NULL, 'TOKENS', NULL, NULL),
-- o1-mini-2024-09-12
('cm10iw6p20000wgx7it1hlb22', NULL, 'o1-mini-2024-09-12', '(?i)^(o1-mini-2024-09-12)$', NULL, 0.000003, 0.000012, NULL, 'TOKENS', NULL, NULL)
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Account" ADD COLUMN "refresh_token_expires_in" INTEGER;
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "traces_project_id_idx"; -- covered by traces_project_id_timestamp_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "datasets_project_id_idx"; -- covered by datasets_project_id_name_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "eval_templates_project_id_idx"; -- covered by eval_templates_project_id_id_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "job_configurations_project_id_idx"; -- covered by job_configurations_project_id_id_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "job_executions_project_id_idx"; -- covered by job_executions_project_id_id_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "llm_api_keys_project_id_provider_idx"; -- covered by llm_api_keys_project_id_provider_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "models_project_id_model_name_idx"; -- covered by models_project_id_model_name_start_date_unit_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "models_project_id_model_name_start_date_unit_idx"; -- covered by models_project_id_model_name_start_date_unit_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "observations_project_id_idx"; -- covered by observations_project_id_start_time_type_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "observations_trace_id_idx"; -- covered by observations_trace_id_project_id_start_time_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "observations_trace_id_project_id_idx"; -- covered by observations_trace_id_project_id_start_time_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "organization_memberships_org_id_idx"; -- covered by organization_memberships_org_id_user_id_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "posthog_integrations_project_id_idx"; -- covered by posthog_integrations_pkey (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "prompts_project_id_idx"; -- covered by prompts_project_id_name_version_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "prompts_project_id_name_version_idx"; -- covered by prompts_project_id_name_version_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "scores_project_id_idx"; -- covered by scores_project_id_name_idx
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Account" ADD COLUMN "created_at" INTEGER;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "scores" ADD COLUMN "queue_id" TEXT;
@@ -0,0 +1,75 @@
-- CreateEnum
CREATE TYPE "AnnotationQueueStatus" AS ENUM ('PENDING', 'COMPLETED');
-- CreateEnum
CREATE TYPE "AnnotationQueueObjectType" AS ENUM ('TRACE', 'OBSERVATION');
-- CreateTable
CREATE TABLE "annotation_queues" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"score_config_ids" TEXT[] DEFAULT ARRAY[]::TEXT[],
"project_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "annotation_queues_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "annotation_queue_items" (
"id" TEXT NOT NULL,
"queue_id" TEXT NOT NULL,
"object_id" TEXT NOT NULL,
"object_type" "AnnotationQueueObjectType" NOT NULL,
"status" "AnnotationQueueStatus" NOT NULL DEFAULT 'PENDING',
"locked_at" TIMESTAMP(3),
"locked_by_user_id" TEXT,
"annotator_user_id" TEXT,
"completed_at" TIMESTAMP(3),
"project_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "annotation_queue_items_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "annotation_queues_id_project_id_idx" ON "annotation_queues"("id", "project_id");
-- CreateIndex
CREATE INDEX "annotation_queues_project_id_created_at_idx" ON "annotation_queues"("project_id", "created_at");
-- CreateIndex
CREATE UNIQUE INDEX "annotation_queues_project_id_name_key" ON "annotation_queues"("project_id", "name");
-- CreateIndex
CREATE INDEX "annotation_queue_items_id_project_id_idx" ON "annotation_queue_items"("id", "project_id");
-- CreateIndex
CREATE INDEX "annotation_queue_items_project_id_queue_id_status_idx" ON "annotation_queue_items"("project_id", "queue_id", "status");
-- CreateIndex
CREATE INDEX "annotation_queue_items_object_id_object_type_project_id_que_idx" ON "annotation_queue_items"("object_id", "object_type", "project_id", "queue_id");
-- CreateIndex
CREATE INDEX "annotation_queue_items_annotator_user_id_idx" ON "annotation_queue_items"("annotator_user_id");
-- CreateIndex
CREATE INDEX "annotation_queue_items_created_at_idx" ON "annotation_queue_items"("created_at");
-- AddForeignKey
ALTER TABLE "annotation_queues" ADD CONSTRAINT "annotation_queues_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_queue_id_fkey" FOREIGN KEY ("queue_id") REFERENCES "annotation_queues"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_locked_by_user_id_fkey" FOREIGN KEY ("locked_by_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_annotator_user_id_fkey" FOREIGN KEY ("annotator_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+100 -56
View File
@@ -34,21 +34,23 @@ generator kysely {
// Necessary for Next auth
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String
refresh_token String? // @db.Text
access_token String? // @db.Text
expires_at Int?
expires_in Int?
ext_expires_in Int?
token_type String?
scope String?
id_token String? // @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String
refresh_token String? // @db.Text
access_token String? // @db.Text
expires_at Int?
expires_in Int?
ext_expires_in Int?
token_type String?
scope String?
id_token String? // @db.Text
session_state String?
refresh_token_expires_in Int?
created_at Int? // GitLab
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@index([userId])
@@ -78,6 +80,8 @@ model User {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
featureFlags String[] @default([]) @map("feature_flags")
annotatedLockedItem AnnotationQueueItem[] @relation("LockedByUser")
annotatedCompletedItem AnnotationQueueItem[] @relation("AnnotatorUser")
@@map("users")
}
@@ -105,31 +109,33 @@ model Organization {
}
model Project {
id String @id @default(cuid())
orgId String @map("org_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
name String
projectMembers ProjectMembership[]
organization Organization @relation(fields: [orgId], references: [id], onUpdate: Cascade, onDelete: Cascade)
traces Trace[]
observations Observation[]
apiKeys ApiKey[]
dataset Dataset[]
RawEvents Events[]
invitations MembershipInvitation[]
sessions TraceSession[]
Prompt Prompt[]
Model Model[]
EvalTemplate EvalTemplate[]
JobConfiguration JobConfiguration[]
JobExecution JobExecution[]
LlmApiKeys LlmApiKeys[]
PosthogIntegration PosthogIntegration[]
Score Score[]
scoreConfig ScoreConfig[]
BatchExport BatchExport[]
comment Comment[]
id String @id @default(cuid())
orgId String @map("org_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
name String
projectMembers ProjectMembership[]
organization Organization @relation(fields: [orgId], references: [id], onUpdate: Cascade, onDelete: Cascade)
traces Trace[]
observations Observation[]
apiKeys ApiKey[]
dataset Dataset[]
RawEvents Events[]
invitations MembershipInvitation[]
sessions TraceSession[]
Prompt Prompt[]
Model Model[]
EvalTemplate EvalTemplate[]
JobConfiguration JobConfiguration[]
JobExecution JobExecution[]
LlmApiKeys LlmApiKeys[]
PosthogIntegration PosthogIntegration[]
Score Score[]
scoreConfig ScoreConfig[]
BatchExport BatchExport[]
comment Comment[]
annotationQueue AnnotationQueue[]
annotationQueueItem AnnotationQueueItem[]
@@index([orgId])
@@map("projects")
@@ -172,7 +178,6 @@ model LlmApiKeys {
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@unique([projectId, provider])
@@index([projectId, provider])
@@map("llm_api_keys")
}
@@ -189,7 +194,6 @@ model OrganizationMembership {
@@unique([orgId, userId])
@@index([userId])
@@index([orgId])
@@map("organization_memberships")
}
@@ -282,7 +286,6 @@ model Trace {
JobExecution JobExecution[]
@@index([projectId])
@@index([projectId, timestamp])
@@index([sessionId])
@@index([name])
@@ -373,12 +376,9 @@ model Observation {
@@index([projectId, internalModel, startTime, unit])
@@index([traceId, projectId, type, startTime])
@@index([traceId, projectId, startTime])
@@index([traceId, projectId])
@@index([traceId])
@@index([type])
@@index(startTime)
@@index(createdAt)
@@index(projectId)
@@index(model)
@@index(internalModel)
@@index([projectId, promptId])
@@ -464,6 +464,7 @@ model Score {
observationId String? @map("observation_id")
configId String? @map("config_id")
stringValue String? @map("string_value") // always defined if data type is CATEGORICAL or BOOLEAN, null for NUMERIC
queueId String? @map("queue_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
dataType ScoreDataType @default(NUMERIC) @map("data_type")
@@ -473,7 +474,6 @@ model Score {
@@unique([id, projectId]) // used for upserts via prisma
@@index(timestamp)
@@index([value])
@@index([projectId])
@@index([projectId, name])
@@index([authorUserId])
@@index([configId])
@@ -521,6 +521,59 @@ enum ScoreDataType {
BOOLEAN
}
model AnnotationQueue {
id String @id @default(cuid())
name String
description String?
scoreConfigIds String[] @default([]) @map("score_config_ids")
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
annotationQueueItem AnnotationQueueItem[]
@@unique([projectId, name])
@@index([id, projectId])
@@index([projectId, createdAt])
@@map("annotation_queues")
}
model AnnotationQueueItem {
id String @id @default(cuid())
queueId String @map("queue_id")
queue AnnotationQueue @relation(fields: [queueId], references: [id], onDelete: Cascade)
objectId String @map("object_id")
objectType AnnotationQueueObjectType @map("object_type")
status AnnotationQueueStatus @default(PENDING)
lockedAt DateTime? @map("locked_at")
lockedByUserId String? @map("locked_by_user_id")
lockedByUser User? @relation("LockedByUser", fields: [lockedByUserId], references: [id], onDelete: SetNull)
annotatorUserId String? @map("annotator_user_id")
annotatorUser User? @relation("AnnotatorUser", fields: [annotatorUserId], references: [id], onDelete: SetNull)
completedAt DateTime? @map("completed_at")
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
@@index([id, projectId])
@@index([projectId, queueId, status])
@@index([objectId, objectType, projectId, queueId])
@@index([annotatorUserId])
@@index([createdAt])
@@map("annotation_queue_items")
}
enum AnnotationQueueStatus {
PENDING
COMPLETED
}
enum AnnotationQueueObjectType {
TRACE
OBSERVATION
}
model CronJobs {
name String @id
lastRun DateTime? @map("last_run")
@@ -544,7 +597,6 @@ model Dataset {
@@id([id, projectId])
@@unique([projectId, name])
@@index([projectId], type: Hash)
@@index([createdAt])
@@index([updatedAt])
@@map("datasets")
@@ -678,9 +730,7 @@ model Prompt {
labels String[] @default([])
@@unique([projectId, name, version])
@@index([projectId, name, version])
@@index([projectId, id])
@@index([projectId])
@@index([createdAt])
@@index([updatedAt])
@@index([tags(ops: ArrayOps)], type: Gin)
@@ -707,8 +757,6 @@ model Model {
tokenizerConfig Json? @map("tokenizer_config")
@@unique([projectId, modelName, startDate, unit])
@@index([projectId, modelName, startDate, unit])
@@index([projectId, modelName])
@@index(modelName)
@@map("models")
}
@@ -757,7 +805,6 @@ model EvalTemplate {
@@unique([projectId, name, version])
@@index([projectId, id])
@@index([projectId])
@@map("eval_templates")
}
@@ -792,7 +839,6 @@ model JobConfiguration {
JobExecution JobExecution[]
@@index([projectId, id])
@@index([projectId])
@@map("job_configurations")
}
@@ -827,7 +873,6 @@ model JobExecution {
@@index([projectId, status])
@@index([projectId, id])
@@index([projectId])
@@index([jobConfigurationId])
@@index([jobOutputScoreId])
@@index([jobInputTraceId])
@@ -860,7 +905,6 @@ model PosthogIntegration {
enabled Boolean
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId])
@@map("posthog_integrations")
}
+154 -24
View File
@@ -5,6 +5,7 @@ import {
ObservationType,
ScoreSource,
ScoreDataType,
AnnotationQueueObjectType,
} from "../src/index";
import { hash } from "bcryptjs";
import { parseArgs } from "node:util";
@@ -12,7 +13,7 @@ import { parseArgs } from "node:util";
import { chunk } from "lodash";
import { v4 } from "uuid";
import { ModelUsageUnit } from "../src";
import { getDisplaySecretKey, hashSecretKey } from "../src/server";
import { getDisplaySecretKey, hashSecretKey, logger } from "../src/server";
import { encrypt } from "../src/encryption";
import { redis } from "../src/server/redis/redis";
@@ -77,6 +78,9 @@ async function main() {
create: {
id: seedOrgId,
name: "Seed Org",
cloudConfig: {
plan: "Team",
},
},
});
@@ -250,6 +254,11 @@ async function main() {
project2,
]);
const queueIds = await generateQueuesForProject(
[project1, project2],
configIdsAndNames
);
const promptIds = await generatePromptsForProject([project1, project2]);
const envTags = [null, "development", "staging", "production"];
@@ -257,18 +266,26 @@ async function main() {
const traceVolume = environment === "load" ? LOAD_TRACE_VOLUME : 100;
const { traces, observations, scores, sessions, events, comments } =
createObjects(
traceVolume,
envTags,
colorTags,
project1,
project2,
promptIds,
configIdsAndNames
);
const {
traces,
observations,
scores,
sessions,
events,
comments,
queueItems,
} = createObjects(
traceVolume,
envTags,
colorTags,
project1,
project2,
promptIds,
queueIds,
configIdsAndNames
);
console.log(
logger.info(
`Seeding ${traces.length} traces, ${observations.length} observations, and ${scores.length} scores`
);
@@ -278,7 +295,8 @@ async function main() {
scores,
sessions,
events,
comments
comments,
queueItems
);
// If openai key is in environment, add it to the projects LLM API keys
@@ -295,7 +313,7 @@ async function main() {
},
});
} else {
console.warn(
logger.warn(
"No OPENAI_API_KEY found in environment. Skipping seeding LLM API key."
);
}
@@ -457,13 +475,13 @@ main()
.then(async () => {
await prisma.$disconnect();
redis?.disconnect();
console.log("Disconnected from postgres and redis");
logger.info("Disconnected from postgres and redis");
})
.catch(async (e) => {
console.error(e);
logger.error(e);
await prisma.$disconnect();
redis?.disconnect();
console.log("Disconnected from postgres and redis");
logger.info("Disconnected from postgres and redis");
process.exit(1);
});
@@ -473,7 +491,8 @@ async function uploadObjects(
scores: Prisma.ScoreCreateManyInput[],
sessions: Prisma.TraceSessionCreateManyInput[],
events: Prisma.ObservationCreateManyInput[],
comments: Prisma.CommentCreateManyInput[]
comments: Prisma.CommentCreateManyInput[],
queueItems: Prisma.AnnotationQueueItemCreateManyInput[]
) {
let promises: Prisma.PrismaPromise<unknown>[] = [];
@@ -493,7 +512,7 @@ async function uploadObjects(
for (let i = 0; i < promises.length; i++) {
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
console.log(
logger.info(
`Seeding of Sessions ${((i + 1) / promises.length) * 100}% complete`
);
await promises[i];
@@ -510,7 +529,7 @@ async function uploadObjects(
});
for (let i = 0; i < promises.length; i++) {
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
console.log(
logger.info(
`Seeding of Traces ${((i + 1) / promises.length) * 100}% complete`
);
await promises[i];
@@ -527,7 +546,7 @@ async function uploadObjects(
for (let i = 0; i < promises.length; i++) {
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
console.log(
logger.info(
`Seeding of Observations ${((i + 1) / promises.length) * 100}% complete`
);
await promises[i];
@@ -544,7 +563,7 @@ async function uploadObjects(
for (let i = 0; i < promises.length; i++) {
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
console.log(
logger.info(
`Seeding of Events ${((i + 1) / promises.length) * 100}% complete`
);
await promises[i];
@@ -560,7 +579,7 @@ async function uploadObjects(
});
for (let i = 0; i < promises.length; i++) {
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
console.log(
logger.info(
`Seeding of Scores ${((i + 1) / promises.length) * 100}% complete`
);
await promises[i];
@@ -576,11 +595,27 @@ async function uploadObjects(
});
for (let i = 0; i < promises.length; i++) {
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
console.log(
logger.info(
`Seeding of Comments ${((i + 1) / promises.length) * 100}% complete`
);
await promises[i];
}
promises = [];
chunk(queueItems, chunkSize).forEach((chunk) => {
promises.push(
prisma.annotationQueueItem.createMany({
data: chunk,
})
);
});
for (let i = 0; i < promises.length; i++) {
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
logger.info(
`Seeding of Annotation Queue Items ${((i + 1) / promises.length) * 100}% complete`
);
await promises[i];
}
}
function createObjects(
@@ -590,6 +625,7 @@ function createObjects(
project1: Project,
project2: Project,
promptIds: Map<string, string[]>,
queueIds: Map<string, string[]>,
configParams: Map<
string,
{
@@ -607,6 +643,7 @@ function createObjects(
const events: Prisma.ObservationCreateManyInput[] = [];
const configs: Prisma.ScoreConfigCreateManyInput[] = [];
const comments: Prisma.CommentCreateManyInput[] = [];
const queueItems: Prisma.AnnotationQueueItemCreateManyInput[] = [];
for (let i = 0; i < traceVolume; i++) {
// print progress to console with a progress bar that refreshes every 10 iterations
@@ -689,6 +726,23 @@ function createObjects(
}),
};
const queueItem = [
...(Math.random() > 0.9
? [
{
queueId: queueIds.get(projectId)?.[
Math.floor(Math.random() * 2)
] as string,
objectId: trace.id,
objectType: AnnotationQueueObjectType.TRACE,
projectId,
},
]
: []),
];
queueItems.push(...queueItem);
const traceScores = [
...(Math.random() > 0.5
? [
@@ -941,6 +995,7 @@ function createObjects(
observations,
scores,
configs,
queueItems,
sessions: uniqueSessions,
events,
comments,
@@ -1218,6 +1273,7 @@ async function generateConfigs(project: Project) {
return configNameAndId;
}
function getGenerationInputOutput(): {
input: Prisma.InputJsonValue;
output: Prisma.InputJsonValue;
@@ -1278,3 +1334,77 @@ function getGenerationInputOutput(): {
return { input, output };
}
async function generateQueuesForProject(
projects: Project[],
configIdsAndNames: Map<
string,
{
name: string;
id: string;
dataType: ScoreDataType;
categories: ConfigCategory[] | null;
}[]
>
) {
const projectIdsToQueues: Map<string, string[]> = new Map();
await Promise.all(
projects.map(async (project) => {
const queueIds = await generateQueues(
project,
configIdsAndNames.get(project.id) ?? []
);
projectIdsToQueues.set(project.id, queueIds);
})
);
return projectIdsToQueues;
}
async function generateQueues(
project: Project,
configIdsAndNames: {
name: string;
id: string;
dataType: ScoreDataType;
categories: ConfigCategory[] | null;
}[]
) {
const queueIds: string[] = [];
const queues = [
{
id: `queue-${v4()}`,
name: "Default",
description: "Default queue",
scoreConfigIds: configIdsAndNames.map((config) => config.id),
projectId: project.id,
},
{
id: `queue-${v4()}`,
name: "Correction",
scoreConfigIds: [configIdsAndNames[0].id],
projectId: project.id,
},
];
for (const queue of queues) {
await prisma.annotationQueue.upsert({
where: {
projectId_name: {
projectId: queue.projectId,
name: queue.name,
},
},
create: {
...queue,
},
update: {
id: queue.id,
},
});
queueIds.push(queue.id);
}
return queueIds;
}
+79 -22
View File
@@ -1,7 +1,7 @@
// This file exports the prisma db connection, the Prisma Object, and the Typescript types.
// This is not imported in the index.ts file of this package, as we must not import this into FE code.
import { PrismaClient } from "@prisma/client";
import { Prisma, PrismaClient } from "@prisma/client";
import { env } from "process";
import kyselyExtension from "prisma-extension-kysely";
import {
@@ -11,21 +11,83 @@ import {
PostgresQueryCompiler,
} from "kysely";
import { DB } from ".";
import { logger } from "./server";
// Instantiated according to the Prisma documentation
// https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices
export class PrismaClientSingleton {
private static instance: PrismaClient;
const prismaClientSingleton = () => {
return new PrismaClient({
log:
env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error", "warn"],
});
};
public static getInstance(): PrismaClient {
if (PrismaClientSingleton.instance) {
return PrismaClientSingleton.instance;
}
const kyselySingleton = (prismaClient: PrismaClient) => {
return prismaClient.$extends(
const client = new PrismaClient<
Prisma.PrismaClientOptions,
"warn" | "error" | "query"
>({
log: [
{ emit: "event", level: "query" },
{ emit: "event", level: "error" },
{ emit: "event", level: "warn" },
],
});
if (env.NODE_ENV === "development") {
client.$on("query", (event) => {
logger.info(`prisma:query ${event.query}, ${event.duration}ms`);
});
}
client.$on("warn", (event) => {
logger.warn(`prisma:warn ${event.message}`);
});
client.$on("error", (event) => {
logger.error(`prisma:error ${event.message}`);
});
PrismaClientSingleton.instance = client;
return PrismaClientSingleton.instance;
}
}
export class KyselySingleton {
private static instance: { $kysely: Kysely<DB> };
public static getInstance() {
if (KyselySingleton.instance) {
return KyselySingleton.instance;
}
KyselySingleton.instance = PrismaClientSingleton.getInstance().$extends(
kyselyExtension({
kysely: (driver) =>
new Kysely<DB>({
dialect: {
// This is where the magic happens!
createDriver: () => driver,
// Don't forget to customize these to match your database!
createAdapter: () => new PostgresAdapter(),
createIntrospector: (db) => new PostgresIntrospector(db),
createQueryCompiler: () => new PostgresQueryCompiler(),
},
}),
})
);
return KyselySingleton.instance;
}
}
declare const globalThis: {
prismaGlobal: PrismaClient | undefined;
kyselyPrismaGlobal: { $kysely: Kysely<DB> } | undefined;
} & typeof global;
if (process.env.NODE_ENV === "development") {
globalThis.prismaGlobal ??= new PrismaClient(); // regular instantiation
globalThis.kyselyPrismaGlobal ??= globalThis.prismaGlobal.$extends(
kyselyExtension({
kysely: (driver) =>
new Kysely<DB>({
@@ -41,15 +103,10 @@ const kyselySingleton = (prismaClient: PrismaClient) => {
})
);
};
declare global {
// eslint-disable-next-line no-var
var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
var kyselyPrisma: undefined | ReturnType<typeof kyselySingleton>;
}
export const prisma = globalThis.prisma ?? prismaClientSingleton();
export const kyselyPrisma = globalThis.kyselyPrisma ?? kyselySingleton(prisma);
export const prisma =
globalThis.prismaGlobal ?? PrismaClientSingleton.getInstance();
export const kyselyPrisma =
globalThis.kyselyPrismaGlobal ?? KyselySingleton.getInstance();
export * from "@prisma/client";
if (process.env.NODE_ENV !== "production") globalThis.prisma = prisma;
+7 -1
View File
@@ -1,4 +1,5 @@
import { z } from "zod";
import { removeEmptyEnvVariables } from "./utils/environment";
const EnvSchema = z.object({
NODE_ENV: z
@@ -38,6 +39,11 @@ const EnvSchema = z.object({
.number()
.positive()
.default(60 * 10),
SALT: z.string().optional(), // used by components imported by web package
LANGFUSE_LOG_LEVEL: z
.enum(["trace", "debug", "info", "warn", "error", "fatal"])
.optional(),
LANGFUSE_LOG_FORMAT: z.enum(["text", "json"]).default("text"),
});
export const env = EnvSchema.parse(process.env);
export const env = EnvSchema.parse(removeEmptyEnvVariables(process.env));
@@ -32,6 +32,7 @@ const CreateAnnotationScoreBase = z.object({
configId: z.string().optional(),
observationId: z.string().optional(),
comment: z.string().nullish(),
queueId: z.string().nullish(),
});
const UpdateAnnotationScoreBase = CreateAnnotationScoreBase.extend({
@@ -57,3 +58,15 @@ export const UpdateAnnotationScoreData = z.discriminatedUnion("dataType", [
UpdateAnnotationScoreBase.merge(CategoricalData),
UpdateAnnotationScoreBase.merge(BooleanData),
]);
// annotation queues
export const CreateQueueData = z.object({
name: z.string().min(1).max(35),
description: z.string().max(1000).optional(),
scoreConfigIds: z.array(z.string()).min(1, {
message: "At least 1 score config must be selected",
}),
});
export type CreateQueue = z.infer<typeof CreateQueueData>;
@@ -19,6 +19,8 @@ export enum BatchExportFileFormat {
export enum BatchExportTableName {
Sessions = "sessions",
Traces = "traces",
Generations = "generations",
}
export const exportOptions: Record<
@@ -83,13 +83,13 @@ export const ScoreBodyWithoutConfig = z.discriminatedUnion("dataType", [
z.object({
value: z.number(),
dataType: z.literal("NUMERIC"),
})
}),
),
BaseScoreBody.merge(
z.object({
value: z.string(),
dataType: z.literal("CATEGORICAL"),
})
}),
),
BaseScoreBody.merge(
z.object({
@@ -97,7 +97,7 @@ export const ScoreBodyWithoutConfig = z.discriminatedUnion("dataType", [
message: "Value must be either 0 or 1",
}),
dataType: z.literal("BOOLEAN"),
})
}),
),
]);
@@ -161,7 +161,7 @@ export const ScorePropsAgainstConfig = z.union([
*/
export const filterAndValidateDbScoreList = (
scores: Score[],
onParseError?: (error: z.ZodError) => void
onParseError?: (error: z.ZodError) => void,
): APIScore[] =>
scores.reduce((acc, ts) => {
const result = APIScoreSchema.safeParse(ts);
@@ -198,14 +198,14 @@ export const PostScoresBody = z.discriminatedUnion("dataType", [
value: z.number(),
dataType: z.literal("NUMERIC"),
configId: z.string().nullish(),
})
}),
),
BaseScoreBody.merge(
z.object({
value: z.string(),
dataType: z.literal("CATEGORICAL"),
configId: z.string().nullish(),
})
}),
),
BaseScoreBody.merge(
z.object({
@@ -215,14 +215,14 @@ export const PostScoresBody = z.discriminatedUnion("dataType", [
}),
dataType: z.literal("BOOLEAN"),
configId: z.string().nullish(),
})
}),
),
BaseScoreBody.merge(
z.object({
value: z.union([z.string(), z.number()]),
dataType: z.undefined(),
configId: z.string().nullish(),
})
}),
),
]);
@@ -256,7 +256,7 @@ const LegacyGetScoreResponseDataV1 = z.intersection(
trace: z.object({
userId: z.string().nullish(),
}),
})
}),
);
export const GetScoresResponse = z.object({
data: z.array(LegacyGetScoreResponseDataV1),
@@ -265,7 +265,7 @@ export const GetScoresResponse = z.object({
export const legacyFilterAndValidateV1GetScoreList = (
scores: unknown[],
onParseError?: (error: z.ZodError) => void
onParseError?: (error: z.ZodError) => void,
): z.infer<typeof LegacyGetScoreResponseDataV1>[] =>
scores.reduce(
(acc: z.infer<typeof LegacyGetScoreResponseDataV1>[], ts) => {
@@ -278,7 +278,7 @@ export const legacyFilterAndValidateV1GetScoreList = (
}
return acc;
},
[] as z.infer<typeof LegacyGetScoreResponseDataV1>[]
[] as z.infer<typeof LegacyGetScoreResponseDataV1>[],
);
// GET /scores/{scoreId}
+3 -4
View File
@@ -1,15 +1,12 @@
export * from "./constants";
export * from "./queries";
export * from "./interfaces/filters";
export * from "./interfaces/orderBy";
export * from "./interfaces/cloudConfigSchema";
export * from "./interfaces/parseDbOrg";
export * from "./tableDefinitions";
export * from "./types";
export * from "./filterToPrisma";
export * from "./orderByToPrisma";
export * from "./tracesTable";
export * from "./server/auth/auth";
export * from "./server/auth/apiKeys";
export * from "./observationsTable";
export * from "./utils/zod";
export * from "./utils/json";
@@ -43,3 +40,5 @@ export { type DB } from "../prisma/generated/types";
// errors
export * from "./errors/index";
export * from "./utils/environment";
@@ -6,6 +6,7 @@ export const RateLimitResource = z.enum([
"public-api",
"public-api-metrics",
"prompts",
"legacy-ingestion",
]);
// result of the rate limit check.
+5 -4
View File
@@ -52,7 +52,8 @@ export const observationsTableCols: ColumnDefinition[] = [
name: "Time To First Token (s)",
id: "timeToFirstToken",
type: "number",
internal: 'o."completion_start_time" - o."start_time"',
internal:
'EXTRACT(EPOCH FROM (o."completion_start_time" - o."start_time"))',
nullable: true,
},
{
@@ -62,10 +63,10 @@ export const observationsTableCols: ColumnDefinition[] = [
internal: '"latency"',
},
{
name: "Time Per Output Token (s)",
id: "timePerOutputToken",
name: "Tokens per second",
id: "tokensPerSecond",
type: "number",
internal: '"latency" / o."completion_tokens"',
internal: 'o."completion_tokens" / "latency"',
nullable: true,
},
{
-1
View File
@@ -1 +0,0 @@
export { createSessionsAllQuery } from "./createSessionsAllQuery";
@@ -0,0 +1,81 @@
import { PrismaClient } from "@prisma/client";
import { compare, hash } from "bcryptjs";
import { randomUUID } from "crypto";
import * as crypto from "crypto";
import { env } from "../../env";
export function getDisplaySecretKey(secretKey: string) {
return secretKey.slice(0, 6) + "..." + secretKey.slice(-4);
}
export async function hashSecretKey(key: string) {
// legacy, uses bcrypt, transformed into hashed key upon first use
const hashedKey = await hash(key, 11);
return hashedKey;
}
async function generateKeySet() {
return {
pk: `pk-lf-${randomUUID()}`,
sk: `sk-lf-${randomUUID()}`,
};
}
export async function verifySecretKey(key: string, hashedKey: string) {
const isValid = await compare(key, hashedKey);
return isValid;
}
export function createShaHash(privateKey: string, salt: string): string {
const hash = crypto
.createHash("sha256")
.update(privateKey)
.update(crypto.createHash("sha256").update(salt, "utf8").digest("hex"))
.digest("hex");
return hash;
}
export async function createAndAddApiKeysToDb(p: {
prisma: PrismaClient;
projectId: string;
note?: string;
predefinedKeys?: {
secretKey: string;
publicKey: string;
};
}) {
const salt = env.SALT;
if (!salt) {
throw new Error("SALT is not set");
}
const { pk, sk } = p.predefinedKeys
? { pk: p.predefinedKeys.publicKey, sk: p.predefinedKeys.secretKey }
: await generateKeySet();
const hashedSk = await hashSecretKey(sk);
const displaySk = getDisplaySecretKey(sk);
const hashFromProvidedKey = createShaHash(sk, salt);
const apiKey = await p.prisma.apiKey.create({
data: {
projectId: p.projectId,
publicKey: pk,
hashedSecretKey: hashedSk,
displaySecretKey: displaySk,
fastHashedSecretKey: hashFromProvidedKey,
note: p.note,
},
});
return {
id: apiKey.id,
createdAt: apiKey.createdAt,
note: apiKey.note,
publicKey: apiKey.publicKey,
secretKey: sk,
displaySecretKey: displaySk,
};
}
-81
View File
@@ -1,81 +0,0 @@
import { compare, hash } from "bcryptjs";
import { randomUUID } from "crypto";
import * as crypto from "crypto";
import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers/oauth";
export function generateSecretKey() {
return `sk-lf-${randomUUID()}`;
}
export function generatePublicKey() {
return `pk-lf-${randomUUID()}`;
}
export function getDisplaySecretKey(secretKey: string) {
return secretKey.slice(0, 6) + "..." + secretKey.slice(-4);
}
export async function hashSecretKey(key: string) {
// legacy, uses bcrypt, transformed into hashed key upon first use
const hashedKey = await hash(key, 11);
return hashedKey;
}
export async function generateKeySet() {
const pk = generatePublicKey();
const sk = generateSecretKey();
const hashedSk = await hashSecretKey(sk);
const displaySk = getDisplaySecretKey(sk);
return {
pk,
sk,
hashedSk,
displaySk,
};
}
export async function verifySecretKey(key: string, hashedKey: string) {
const isValid = await compare(key, hashedKey);
return isValid;
}
export function createShaHash(privateKey: string, salt: string): string {
const hash = crypto
.createHash("sha256")
.update(privateKey)
.update(crypto.createHash("sha256").update(salt, "utf8").digest("hex"))
.digest("hex");
return hash;
}
export interface CustomSSOUser extends Record<string, any> {
email: string;
id: string;
name: string;
verified: boolean;
}
export function CustomSSOProvider<P extends CustomSSOUser>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "custom",
name: "CustomSSOProvider",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid email profile" } }, // overridden by options.authorization to be able to set custom scopes, deep merged with this default
checks: ["pkce", "state"],
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
};
},
options,
};
}
@@ -0,0 +1,31 @@
import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers/oauth";
interface CustomSSOUser extends Record<string, any> {
email: string;
id: string;
name: string;
verified: boolean;
}
export function CustomSSOProvider<P extends CustomSSOUser>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "custom",
name: "CustomSSOProvider",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid email profile" } }, // overridden by options.authorization to be able to set custom scopes, deep merged with this default
checks: ["pkce", "state"],
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
};
},
options,
};
}
@@ -1,8 +1,9 @@
import { Prisma } from "@prisma/client";
import { ColumnDefinition, type TableNames } from "./tableDefinitions";
import { FilterState } from "./types";
import { filterOperators, timeFilter } from "./interfaces/filters";
import { ColumnDefinition, type TableNames } from "../tableDefinitions";
import { FilterState } from "../types";
import { filterOperators, timeFilter } from "../interfaces/filters";
import { z } from "zod";
import { logger } from "./index";
const operatorReplacements = {
"any of": "IN",
@@ -51,7 +52,7 @@ export function tableColumnsToSqlFilter(
c.name === filter.column || c.id === filter.column
);
if (!col) {
console.error("Invalid filter column", filter.column);
logger.error("Invalid filter column", filter.column);
throw new Error("Invalid filter column: " + filter.column);
}
const colPrisma = Prisma.raw(col.internal);
+6 -1
View File
@@ -3,7 +3,8 @@ export * from "./services/email/organizationInvitation/sendMembershipInvitationE
export * from "./services/email/batchExportSuccess/sendBatchExportSuccessEmail";
export * from "./services/email/passwordReset/sendResetPasswordVerificationRequest";
export * from "./services/PromptService";
export * from "./auth/auth";
export * from "./auth/apiKeys";
export * from "./auth/customSsoProvider";
export * from "./llm/fetchLLMCompletion";
export * from "./llm/types";
export * from "./utils/DatabaseReadStream";
@@ -24,4 +25,8 @@ export * from "./auth/types";
export * from "./ingestion/legacy/index";
export * from "./queues";
export * from "./ingestion/legacy/EventProcessor";
export * from "./orderByToPrisma";
export * from "./filterToPrisma";
export * from "./instrumentation";
export * from "./logger";
export * from "./queries";
@@ -10,6 +10,7 @@ import {
traceEvent,
scoreEvent,
sdkLogEvent,
ingestionEvent,
} from "../types";
import { validateAndInflateScore } from "../validateAndInflateScore";
import { Trace, Observation, Score, Prisma, Model } from "@prisma/client";
@@ -18,15 +19,42 @@ import { mergeJson } from "../../../utils/json";
import { jsonSchema } from "../../../utils/zod";
import { prisma } from "../../../db";
import { LegacyIngestionAccessScope } from ".";
import { logger } from "../../logger";
export interface EventProcessor {
auth(apiScope: LegacyIngestionAccessScope): void;
process(
apiScope: LegacyIngestionAccessScope
apiScope: LegacyIngestionAccessScope,
): Promise<Trace | Observation | Score> | undefined;
}
export const getProcessorForEvent = (
event: z.infer<typeof ingestionEvent>,
calculateTokenDelegate: (p: {
model: Model;
text: unknown;
}) => number | undefined,
): EventProcessor => {
switch (event.type) {
case eventTypes.TRACE_CREATE:
return new TraceProcessor(event);
case eventTypes.OBSERVATION_CREATE:
case eventTypes.OBSERVATION_UPDATE:
case eventTypes.EVENT_CREATE:
case eventTypes.SPAN_CREATE:
case eventTypes.SPAN_UPDATE:
case eventTypes.GENERATION_CREATE:
case eventTypes.GENERATION_UPDATE:
return new ObservationProcessor(event, calculateTokenDelegate);
case eventTypes.SCORE_CREATE: {
return new ScoreProcessor(event);
}
case eventTypes.SDK_LOG:
return new SdkLogProcessor(event);
}
};
export class ObservationProcessor implements EventProcessor {
event: ObservationEvent;
calculateTokenDelegate: (p: {
@@ -39,7 +67,7 @@ export class ObservationProcessor implements EventProcessor {
calculateTokenDelegate: (p: {
model: Model;
text: unknown;
}) => number | undefined
}) => number | undefined,
) {
this.event = event;
this.calculateTokenDelegate = calculateTokenDelegate;
@@ -47,7 +75,7 @@ export class ObservationProcessor implements EventProcessor {
async convertToObservation(
apiScope: LegacyIngestionAccessScope,
existingObservation: Observation | null
existingObservation: Omit<Observation, "input" | "output"> | null,
): Promise<{
id: string;
create: Prisma.ObservationUncheckedCreateInput;
@@ -77,7 +105,7 @@ export class ObservationProcessor implements EventProcessor {
!existingObservation
) {
throw new LangfuseNotFoundError(
`Observation with id ${this.event.id} not found`
`Observation with id ${this.event.id} not found`,
);
}
@@ -119,11 +147,12 @@ export class ObservationProcessor implements EventProcessor {
// Token counts
const [newInputCount, newOutputCount] =
"usage" in this.event.body
? this.calculateTokenCounts(
? await this.calculateTokenCounts(
apiScope.projectId,
this.event.body,
this.calculateTokenDelegate,
internalModel ?? undefined,
existingObservation ?? undefined
existingObservation ?? undefined,
)
: [undefined, undefined];
@@ -159,7 +188,7 @@ export class ObservationProcessor implements EventProcessor {
const calculatedCosts = ObservationProcessor.calculateTokenCosts(
internalModel,
userProvidedTokenCosts,
tokenCounts
tokenCounts,
);
// merge metadata from existingObservation.metadata and metadata
@@ -167,7 +196,7 @@ export class ObservationProcessor implements EventProcessor {
existingObservation?.metadata
? jsonSchema.parse(existingObservation.metadata)
: undefined,
this.event.body.metadata ?? undefined
this.event.body.metadata ?? undefined,
);
const prompt =
@@ -187,8 +216,9 @@ export class ObservationProcessor implements EventProcessor {
: undefined;
// Only null if promptName and promptVersion are set but prompt is not found
if (prompt === null)
console.warn("Prompt not found for observation", this.event.body);
if (prompt === null) {
logger.warn("Prompt not found for observation", this.event.body);
}
const observationId = this.event.body.id ?? v4();
@@ -309,7 +339,8 @@ export class ObservationProcessor implements EventProcessor {
};
}
calculateTokenCounts(
async calculateTokenCounts(
projectId: string,
body:
| z.infer<typeof legacyObservationCreateEvent>["body"]
| z.infer<typeof generationCreateEvent>["body"],
@@ -318,29 +349,59 @@ export class ObservationProcessor implements EventProcessor {
text: unknown;
}) => number | undefined,
model?: Model,
existingObservation?: Observation
existingObservation?: Omit<Observation, "input" | "output">,
) {
const newPromptTokens =
body.usage?.input ??
((body.input || existingObservation?.input) && model && model.tokenizerId
? calculateTokenDelegate({
model: model,
text: body.input ?? existingObservation?.input,
})
: undefined);
let newPromptTokens = body.usage?.input;
if (newPromptTokens === undefined && model && model.tokenizerId) {
if (body.input) {
newPromptTokens = calculateTokenDelegate({
model: model,
text: body.input,
});
} else {
logger.info(
`No input provided, trying to calculate for id: ${existingObservation?.id}`,
);
const observationInput = await prisma.observation.findFirst({
where: { id: existingObservation?.id, projectId: projectId },
select: {
input: true,
},
});
const newCompletionTokens =
body.usage?.output ??
((body.output || existingObservation?.output) &&
model &&
model.tokenizerId
? calculateTokenDelegate({
model: model,
text: body.output ?? existingObservation?.output,
})
: undefined);
newPromptTokens = calculateTokenDelegate({
model: model,
text: observationInput?.input,
});
}
}
return [newPromptTokens, newCompletionTokens];
let newCompletionTokens = body.usage?.output;
if (newCompletionTokens === undefined && model && model.tokenizerId) {
if (body.output) {
newCompletionTokens = calculateTokenDelegate({
model: model,
text: body.output,
});
} else {
logger.info(
`No output provided, trying to calculate for id: ${existingObservation?.id}`,
);
const observationOutput = await prisma.observation.findFirst({
where: { id: existingObservation?.id, projectId: projectId },
select: {
output: true,
},
});
newCompletionTokens = calculateTokenDelegate({
model: model,
text: observationOutput?.output,
});
}
}
return [newPromptTokens ?? undefined, newCompletionTokens ?? undefined];
}
static calculateTokenCosts(
@@ -350,7 +411,7 @@ export class ObservationProcessor implements EventProcessor {
outputCost?: Decimal | null;
totalCost?: Decimal | null;
},
tokenCounts: { input?: number; output?: number; total?: number }
tokenCounts: { input?: number; output?: number; total?: number },
): {
inputCost?: Decimal | null;
outputCost?: Decimal | null;
@@ -367,7 +428,7 @@ export class ObservationProcessor implements EventProcessor {
totalCost:
userProvidedCosts.totalCost ??
(userProvidedCosts.inputCost ?? new Decimal(0)).add(
userProvidedCosts.outputCost ?? new Decimal(0)
userProvidedCosts.outputCost ?? new Decimal(0),
),
};
}
@@ -408,7 +469,43 @@ export class ObservationProcessor implements EventProcessor {
const existingObservation = this.event.body.id
? await prisma.observation.findFirst({
where: { id: this.event.body.id },
select: {
// do not select I/O to spare our db
input: false,
output: false,
id: true,
traceId: true,
projectId: true,
type: true,
startTime: true,
endTime: true,
name: true,
metadata: true,
parentObservationId: true,
level: true,
statusMessage: true,
version: true,
createdAt: true,
updatedAt: true,
model: true,
internalModelId: true,
modelParameters: true,
promptTokens: true,
completionTokens: true,
totalTokens: true,
unit: true,
inputCost: true,
outputCost: true,
totalCost: true,
calculatedInputCost: true,
calculatedOutputCost: true,
calculatedTotalCost: true,
completionStartTime: true,
promptId: true,
internalModel: true,
},
where: { id: this.event.body.id, projectId: apiScope.projectId },
})
: null;
@@ -417,7 +514,7 @@ export class ObservationProcessor implements EventProcessor {
existingObservation.projectId !== apiScope.projectId
) {
throw new ForbiddenError(
`Access denied for observation creation ${existingObservation.projectId} `
`Access denied for observation creation ${existingObservation.projectId} `,
);
}
@@ -434,6 +531,7 @@ export class ObservationProcessor implements EventProcessor {
});
}
}
export class TraceProcessor implements EventProcessor {
event: z.infer<typeof traceEvent>;
@@ -447,7 +545,7 @@ export class TraceProcessor implements EventProcessor {
}
async process(
apiScope: LegacyIngestionAccessScope
apiScope: LegacyIngestionAccessScope,
): Promise<Trace | Observation | Score> {
const { body } = this.event;
@@ -455,11 +553,8 @@ export class TraceProcessor implements EventProcessor {
const internalId = body.id ?? v4();
console.log(
"Trying to create trace, project ",
apiScope.projectId,
", id:",
internalId
logger.debug(
`Trying to create trace, project ${apiScope.projectId}, id: ${internalId}`,
);
const existingTrace = await prisma.trace.findFirst({
@@ -470,7 +565,7 @@ export class TraceProcessor implements EventProcessor {
if (existingTrace && existingTrace.projectId !== apiScope.projectId) {
throw new ForbiddenError(
`Access denied for trace creation ${existingTrace.projectId}`
`Access denied for trace creation ${existingTrace.projectId}`,
);
}
@@ -478,7 +573,7 @@ export class TraceProcessor implements EventProcessor {
existingTrace?.metadata
? jsonSchema.parse(existingTrace.metadata)
: undefined,
body.metadata ?? undefined
body.metadata ?? undefined,
);
const mergedTags =
@@ -546,6 +641,7 @@ export class TraceProcessor implements EventProcessor {
return upsertedTrace;
}
}
export class ScoreProcessor implements EventProcessor {
event: z.infer<typeof scoreEvent>;
@@ -556,12 +652,12 @@ export class ScoreProcessor implements EventProcessor {
auth(apiScope: LegacyIngestionAccessScope) {
if (apiScope.accessLevel !== "scores" && apiScope.accessLevel !== "all")
throw new ForbiddenError(
`Access denied for score creation, ${apiScope.accessLevel}`
`Access denied for score creation, ${apiScope.accessLevel}`,
);
}
async process(
apiScope: LegacyIngestionAccessScope
apiScope: LegacyIngestionAccessScope,
): Promise<Trace | Observation | Score> {
const { body } = this.event;
@@ -579,7 +675,7 @@ export class ScoreProcessor implements EventProcessor {
});
if (existingScore && existingScore.projectId !== apiScope.projectId) {
throw new ForbiddenError(
`Access denied for score creation ${existingScore.projectId}`
`Access denied for score creation ${existingScore.projectId}`,
);
}
@@ -619,7 +715,7 @@ export class SdkLogProcessor implements EventProcessor {
process() {
try {
console.log("SDK Log", this.event);
logger.info("SDK Log", this.event);
return undefined;
} catch (error) {
return undefined;
@@ -8,10 +8,11 @@ import { IngestionUtils } from "../IngestionUtils";
import { IngestionEventType } from "../types";
import { redis } from "../../redis/redis";
import { env } from "../../../env";
import { logger } from "../../logger";
export async function enqueueIngestionEvents(
projectId: string,
events: IngestionEventType[]
events: IngestionEventType[],
) {
const ingestionFlushQueue = getIngestionFlushQueue();
@@ -31,8 +32,8 @@ export async function enqueueIngestionEvents(
event,
redis,
ingestionFlushQueue,
batchTimestamp
)
batchTimestamp,
),
);
}
@@ -44,11 +45,11 @@ async function enqueueSingleIngestionEvent(
event: IngestionEventType,
redis: Redis,
ingestionFlushQueue: IngestionFlushQueue,
batchTimestamp: string
batchTimestamp: string,
): Promise<void> {
if (!("id" in event.body && event.body.id)) {
console.warn(
`Received ingestion event without id: ${JSON.stringify(event)}`
logger.warn(
`Received ingestion event without id: ${JSON.stringify(event)}`,
);
return;
@@ -2,14 +2,8 @@ import { env } from "node:process";
import z from "zod";
import { ForbiddenError, UnauthorizedError } from "../../../errors";
import { eventTypes, ingestionApiSchema, ingestionEvent } from "../types";
import {
EventProcessor,
TraceProcessor,
ObservationProcessor,
ScoreProcessor,
SdkLogProcessor,
} from "./EventProcessor";
import { EventBodyType, EventName, TraceUpsertEventType } from "../../queues";
import { getProcessorForEvent } from "./EventProcessor";
import { TraceUpsertEventType } from "../../queues";
import {
convertTraceUpsertEventsToRedisEvents,
getTraceUpsertQueue,
@@ -19,6 +13,7 @@ import { redis } from "../../redis/redis";
import { backOff } from "exponential-backoff";
import { Model } from "../../..";
import { enqueueIngestionEvents } from "./enqueueIngestionEvents";
import { logger } from "../../logger";
export type BatchResult = {
result: unknown;
@@ -49,9 +44,9 @@ type LegacyIngestionAuthHeaderVerificationResult =
export const handleBatch = async (
events: z.infer<typeof ingestionApiSchema>["batch"],
authCheck: LegacyIngestionAuthHeaderVerificationResult,
calculateTokenDelegate: (p: TokenCountInput) => number | undefined
calculateTokenDelegate: (p: TokenCountInput) => number | undefined,
) => {
console.log(`handling ingestion ${events.length} events`);
logger.debug(`handling ingestion ${events.length} events`);
if (!authCheck.validKey) throw new UnauthorizedError(authCheck.error);
@@ -69,7 +64,7 @@ export const handleBatch = async (
return await handleSingleEvent(
singleEvent,
authCheck.scope,
calculateTokenDelegate
calculateTokenDelegate,
);
});
results.push({
@@ -79,7 +74,7 @@ export const handleBatch = async (
}); // Push each result into the array
} catch (error) {
// Handle or log the error if `handleSingleEvent` fails
console.error("Error handling event:", error);
logger.error("Error handling event:", error);
// Decide how to handle the error: rethrow, continue, or push an error object to results
// For example, push an error object:
errors.push({
@@ -93,9 +88,9 @@ export const handleBatch = async (
if (env.CLICKHOUSE_URL) {
try {
await enqueueIngestionEvents(authCheck.scope.projectId, events);
console.log(`Added ${events.length} ingestion events to queue`);
logger.info(`Added ${events.length} ingestion events to queue`);
} catch (err) {
console.error("Error adding ingestion events to queue", err);
logger.error("Error adding ingestion events to queue", err);
}
}
@@ -107,10 +102,10 @@ async function retry<T>(request: () => Promise<T>): Promise<T> {
numOfAttempts: env.LANGFUSE_ASYNC_INGESTION_PROCESSING === "true" ? 5 : 3,
retry: (e: Error, attemptNumber: number) => {
if (e instanceof UnauthorizedError || e instanceof ForbiddenError) {
console.log("not retrying auth error");
logger.info("not retrying auth error");
return false;
}
console.log(`retrying processing events ${attemptNumber}`);
logger.info(`retrying processing events ${attemptNumber}`);
return true;
},
});
@@ -122,7 +117,7 @@ const handleSingleEvent = async (
calculateTokenDelegate: (p: {
model: Model;
text: unknown;
}) => number | undefined
}) => number | undefined,
) => {
const { body } = event;
let restEvent = body;
@@ -137,46 +132,24 @@ const handleSingleEvent = async (
restEvent = rest;
}
console.log(
`handling single event ${event.id} of type ${event.type}: ${JSON.stringify({ body: restEvent })}`
logger.info(
`handling single event ${event.id} of type ${event.type}: ${JSON.stringify({ body: restEvent })}`,
);
const cleanedEvent = ingestionEvent.parse(cleanEvent(event));
const { type } = cleanedEvent;
let processor: EventProcessor;
switch (type) {
case eventTypes.TRACE_CREATE:
processor = new TraceProcessor(cleanedEvent);
break;
case eventTypes.OBSERVATION_CREATE:
case eventTypes.OBSERVATION_UPDATE:
case eventTypes.EVENT_CREATE:
case eventTypes.SPAN_CREATE:
case eventTypes.SPAN_UPDATE:
case eventTypes.GENERATION_CREATE:
case eventTypes.GENERATION_UPDATE:
processor = new ObservationProcessor(
cleanedEvent,
calculateTokenDelegate
);
break;
case eventTypes.SCORE_CREATE: {
processor = new ScoreProcessor(cleanedEvent);
break;
}
case eventTypes.SDK_LOG:
processor = new SdkLogProcessor(cleanedEvent);
}
// Deny access to non-score events if the access level is not "all"
// This is an additional safeguard to auth checks in EventProcessor
if (apiScope.accessLevel !== "all" && type !== eventTypes.SCORE_CREATE) {
if (
apiScope.accessLevel !== "all" &&
cleanedEvent.type !== eventTypes.SCORE_CREATE
) {
throw new ForbiddenError("Access denied. Event type not allowed.");
}
return await processor.process(apiScope);
return getProcessorForEvent(cleanedEvent, calculateTokenDelegate).process(
apiScope,
);
};
// cleans NULL characters from the event
@@ -201,15 +174,15 @@ export function cleanEvent(obj: unknown): unknown {
}
export const isNotNullOrUndefined = <T>(
val?: T | null
val?: T | null,
): val is Exclude<T, null | undefined> => !isUndefinedOrNull(val);
export const isUndefinedOrNull = <T>(val?: T | null): val is undefined | null =>
val === undefined || val === null;
export const sendToWorkerIfEnvironmentConfigured = async (
export const addTracesToTraceUpsertQueue = async (
batchResults: BatchResult[],
projectId: string
projectId: string,
): Promise<void> => {
const traceEvents: TraceUpsertEventType[] = batchResults
.filter((result) => result.type === eventTypes.TRACE_CREATE) // we only have create, no update.
@@ -219,49 +192,23 @@ export const sendToWorkerIfEnvironmentConfigured = async (
"id" in result.result
? // ingestion API only gets traces for one projectId
{ traceId: result.result.id as string, projectId }
: null
: null,
)
.filter(isNotNullOrUndefined);
try {
if (env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION && redis) {
console.log(`Sending ${traceEvents.length} events to worker via Redis`);
logger.info(`Sending ${traceEvents.length} events to worker via Redis`);
const queue = getTraceUpsertQueue();
if (!queue) {
console.error("TraceUpsertQueue not initialized");
logger.error("TraceUpsertQueue not initialized");
return;
}
await queue.addBulk(convertTraceUpsertEventsToRedisEvents(traceEvents));
} else if (
env.LANGFUSE_WORKER_HOST &&
env.LANGFUSE_WORKER_PASSWORD &&
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION
) {
console.log(`Sending ${traceEvents.length} events to worker via HTTP`);
const body: EventBodyType = {
name: EventName.TraceUpsert,
payload: traceEvents,
};
if (traceEvents.length > 0) {
await fetch(`${env.LANGFUSE_WORKER_HOST}/api/events`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization:
"Basic " +
Buffer.from(
"admin" + ":" + env.LANGFUSE_WORKER_PASSWORD
).toString("base64"),
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(8 * 1000),
});
}
}
} catch (error) {
console.error("Error sending events to worker", error);
logger.error("Error sending events to worker", error);
}
};
@@ -8,7 +8,7 @@ export async function findModel(p: {
unit?: string;
startTime?: Date;
};
existingDbObservation?: Observation;
existingDbObservation?: Omit<Observation, "input" | "output">;
}): Promise<Model | null> {
const { event, existingDbObservation } = p;
// either get the model from the existing observation
+11 -10
View File
@@ -115,7 +115,7 @@ export const CreateGenerationBody = CreateSpanBody.extend({
z.array(z.string()),
z.record(z.string()),
])
.nullish()
.nullish(),
)
.nullish(),
usage: usage,
@@ -143,7 +143,7 @@ export const UpdateGenerationBody = UpdateSpanBody.extend({
z.array(z.string()),
z.record(z.string()),
])
.nullish()
.nullish(),
)
.nullish(),
usage: usage,
@@ -174,14 +174,14 @@ export const ScoreBody = z.discriminatedUnion("dataType", [
value: z.number(),
dataType: z.literal("NUMERIC"),
configId: z.string().nullish(),
})
}),
),
BaseScoreBody.merge(
z.object({
value: z.string(),
dataType: z.literal("CATEGORICAL"),
configId: z.string().nullish(),
})
}),
),
BaseScoreBody.merge(
z.object({
@@ -191,14 +191,14 @@ export const ScoreBody = z.discriminatedUnion("dataType", [
}),
dataType: z.literal("BOOLEAN"),
configId: z.string().nullish(),
})
}),
),
BaseScoreBody.merge(
z.object({
value: z.union([z.string(), z.number()]),
dataType: z.undefined(),
configId: z.string().nullish(),
})
}),
),
]);
@@ -243,7 +243,7 @@ export const LegacyGenerationsCreateSchema = z.object({
modelParameters: z
.record(
z.string(),
z.union([z.string(), z.number(), z.boolean()]).nullish()
z.union([z.string(), z.number(), z.boolean()]).nullish(),
)
.nullish(),
prompt: jsonSchema.nullish(),
@@ -267,7 +267,7 @@ export const LegacyGenerationPatchSchema = z.object({
modelParameters: z
.record(
z.string(),
z.union([z.string(), z.number(), z.boolean()]).nullish()
z.union([z.string(), z.number(), z.boolean()]).nullish(),
)
.nullish(),
prompt: jsonSchema.nullish(),
@@ -291,7 +291,7 @@ export const LegacyObservationBody = z.object({
modelParameters: z
.record(
z.string(),
z.union([z.string(), z.number(), z.boolean()]).nullish()
z.union([z.string(), z.number(), z.boolean()]).nullish(),
)
.nullish(),
input: jsonSchema.nullish(),
@@ -306,6 +306,7 @@ export const LegacyObservationBody = z.object({
export const SdkLogEvent = z.object({
log: jsonSchema,
id: z.string().nullish(), // Not used, but makes downstream processing easier.
});
// definitions for the ingestion API
@@ -402,7 +403,7 @@ export const ingestionBatchEvent = z.array(ingestionEvent);
export type IngestionBatchEventType = z.infer<typeof ingestionBatchEvent>;
export const ingestionEventWithProjectId = ingestionEvent.and(
z.object({ projectId: z.string() })
z.object({ projectId: z.string() }),
);
export type IngestionEventWithProjectIdType = z.infer<
typeof ingestionEventWithProjectId
@@ -15,7 +15,7 @@ type ValidateAndInflateScoreParams = {
};
export async function validateAndInflateScore(
params: ValidateAndInflateScoreParams
params: ValidateAndInflateScoreParams,
): Promise<Score> {
const { body, projectId } = params;
@@ -29,7 +29,7 @@ export async function validateAndInflateScore(
if (!config || !validateDbScoreConfigSafe(config).success)
throw new LangfuseNotFoundError(
"The configId you provided does not match a valid config in this project"
"The configId you provided does not match a valid config in this project",
);
validateConfigAgainstBody(body, config as ValidatedScoreConfig);
@@ -47,7 +47,7 @@ export async function validateAndInflateScore(
if (!validation.success) {
throw new InvalidRequestError(
`Ingested score value type not valid against provided data type. Provide numeric values for numeric and boolean scores, and string values for categorical scores.`
`Ingested score value type not valid against provided data type. Provide numeric values for numeric and boolean scores, and string values for categorical scores.`,
);
}
@@ -62,7 +62,7 @@ function inferDataType(value: string | number): ScoreDataType {
function mapStringValueToNumericValue(
config: ValidatedScoreConfig,
label: string
label: string,
): number | null {
return (
config.categories?.find((category) => category.label === label)?.value ??
@@ -71,7 +71,7 @@ function mapStringValueToNumericValue(
}
function inflateScoreBody(
params: ValidateAndInflateScoreParams & { config?: ValidatedScoreConfig }
params: ValidateAndInflateScoreParams & { config?: ValidatedScoreConfig },
): Score {
const { body, projectId, scoreId, config } = params;
@@ -105,25 +105,25 @@ function inflateScoreBody(
function validateConfigAgainstBody(
body: any,
config: ValidatedScoreConfig
config: ValidatedScoreConfig,
): void {
const { maxValue, minValue, categories, dataType: configDataType } = config;
if (body.dataType && body.dataType !== configDataType) {
throw new InvalidRequestError(
`Data type mismatch based on config: expected ${configDataType}, got ${body.dataType}`
`Data type mismatch based on config: expected ${configDataType}, got ${body.dataType}`,
);
}
if (config.isArchived) {
throw new InvalidRequestError(
"Config is archived and cannot be used to create new scores. Please restore the config first."
"Config is archived and cannot be used to create new scores. Please restore the config first.",
);
}
if (config.name !== body.name) {
throw new InvalidRequestError(
`Name mismatch based on config: expected ${config.name}, got ${body.name}`
`Name mismatch based on config: expected ${config.name}, got ${body.name}`,
);
}
@@ -136,7 +136,7 @@ function validateConfigAgainstBody(
if (!dataTypeValidation.success) {
throw new InvalidRequestError(
`Ingested score body not valid against provided config data type.`
`Ingested score body not valid against provided config data type.`,
);
}
@@ -154,7 +154,7 @@ function validateConfigAgainstBody(
.join(", ");
throw new InvalidRequestError(
`Ingested score body not valid against provided config: ${errorDetails}`
`Ingested score body not valid against provided config: ${errorDetails}`,
);
}
}
@@ -12,13 +12,3 @@ Throughout our applications we want to use as much Otel as possible. This helps
- `web` and `worker` have an instrumentation.ts file, which configures otel for the application.
- For trpc, we use `@baselime/trpc-opentelemetry-middleware` to enrich spans with trpc inputs and outputs.
- When building adding new infrastructure, we should search for auto instumentations for our code base.
## Library support
- `dd-trace` has a direct dependency of `@opentelemetry/api` version < `1.9` [GH](https://github.com/DataDog/dd-trace-js/blob/ed9b0b30f7b0283579a9bf8c18e1f9deab18fecf/package.json#L81).
- `@prisma/instrumentation` requires at least `@opentelemetry/api` version `1.8` [GH](https://github.com/prisma/prisma/blob/d780290b13754420abcfa5d7592f02049c6cc005/packages/instrumentation/package.json#L25)
- We need to ensure that all other libraries we use are compatible with `@opentelemetry/api` version `1.8`. If we have a package using a later version, this will break the instrumentation and crash the container. We can check the versions with the following command:
```bash
pnpm --filter=web why @opentelemetry/api
```
@@ -3,54 +3,42 @@ import * as dd from "dd-trace";
// type CallbackFn<T> = () => T;
export type TCarrier = {
traceparent?: string;
tracestate?: string;
};
export type SpanCtx = {
name: string;
spanKind?: opentelemetry.SpanKind; // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spankind
rootSpan?: boolean; // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#traces
traceScope?: string;
traceContext?: TCarrier;
};
type AsyncCallbackFn<T> = () => Promise<T>;
type AsyncCallbackFn<T> = (span: opentelemetry.Span) => Promise<T>;
export async function instrumentAsync<T>(
ctx: SpanCtx,
callback: AsyncCallbackFn<T>
callback: AsyncCallbackFn<T>,
): Promise<T> {
return await getTracer(ctx.traceScope ?? callback.name).startActiveSpan(
ctx.name,
{
root: ctx.rootSpan,
kind: ctx.spanKind,
},
async (span) => {
try {
const result = await callback();
span.end();
return result;
} catch (ex) {
traceException(ex as opentelemetry.Exception, span);
span.end();
throw ex;
}
}
);
}
const activeContext = ctx.traceContext
? opentelemetry.propagation.extract(
opentelemetry.context.active(),
ctx.traceContext,
)
: opentelemetry.context.active();
type SyncCallbackFn<T> = () => T;
export function instrumentSync<T>(
ctx: SpanCtx,
callback: SyncCallbackFn<T>
): T {
return getTracer(ctx.traceScope ?? callback.name).startActiveSpan(
ctx.name,
{
root: ctx.rootSpan,
root: !Boolean(ctx.traceContext) && ctx.rootSpan,
kind: ctx.spanKind,
},
(span) => {
activeContext,
async (span) => {
try {
const result = callback();
const result = await callback(span);
span.end();
return result;
} catch (ex) {
@@ -58,7 +46,41 @@ export function instrumentSync<T>(
span.end();
throw ex;
}
}
},
);
}
type SyncCallbackFn<T> = (span: opentelemetry.Span) => T;
export function instrumentSync<T>(
ctx: SpanCtx,
callback: SyncCallbackFn<T>,
): T {
const activeContext = ctx.traceContext
? opentelemetry.propagation.extract(
opentelemetry.context.active(),
ctx.traceContext,
)
: opentelemetry.context.active();
return getTracer(ctx.traceScope ?? callback.name).startActiveSpan(
ctx.name,
{
root: !Boolean(ctx.traceContext) && ctx.rootSpan,
kind: ctx.spanKind,
},
activeContext,
(span) => {
try {
const result = callback(span);
span.end();
return result;
} catch (ex) {
traceException(ex as opentelemetry.Exception, span);
span.end();
throw ex;
}
},
);
}
@@ -67,7 +89,7 @@ export const getCurrentSpan = () => opentelemetry.trace.getActiveSpan();
export const traceException = (
ex: unknown,
span?: opentelemetry.Span,
code?: string
code?: string,
) => {
const activeSpan = span ?? getCurrentSpan();
@@ -115,7 +137,7 @@ export const traceException = (
export const addUserToSpan = (
attributes: { userId?: string; projectId?: string; email?: string },
span?: opentelemetry.Span
span?: opentelemetry.Span,
) => {
const activeSpan = span ?? getCurrentSpan();
@@ -138,7 +160,7 @@ export const recordGauge = (
| {
[tag: string]: string | number;
}
| undefined
| undefined,
) => {
dd.dogstatsd.gauge(stat, value, tags);
};
@@ -146,7 +168,7 @@ export const recordGauge = (
export const recordIncrement = (
stat: string,
value?: number | undefined,
tags?: { [tag: string]: string | number } | undefined
tags?: { [tag: string]: string | number } | undefined,
) => {
dd.dogstatsd.increment(stat, value, tags);
};
@@ -154,7 +176,19 @@ export const recordIncrement = (
export const recordHistogram = (
stat: string,
value?: number | undefined,
tags?: { [tag: string]: string | number } | undefined
tags?: { [tag: string]: string | number } | undefined,
) => {
dd.dogstatsd.histogram(stat, value, tags);
};
/**
* Converts a queue name to the matching datadog metric name.
* Consumer only needs to append the relevant suffix.
*
* Example: `legacy-ingestion-queue` -> `langfuse.queue.legacy_ingestion`
*/
export const convertQueueNameToMetricName = (queueName: string): string => {
return (
"langfuse.queue." + queueName.replace(/-/g, "_").replace(/_queue$/, "")
);
};
@@ -1,3 +1,5 @@
import { type ZodSchema } from "zod";
import { ChatAnthropic } from "@langchain/anthropic";
import {
AIMessage,
@@ -12,20 +14,12 @@ import {
import { IterableReadableStream } from "@langchain/core/utils/stream";
import { ChatOpenAI } from "@langchain/openai";
import {
ChatMessage,
ChatMessageRole,
LLMFunctionCall,
ModelParams,
LLMAdapter,
} from "./types";
import zodToJsonSchema from "zod-to-json-schema";
import { JsonOutputFunctionsParser } from "langchain/output_parsers";
import { ChatMessage, ChatMessageRole, ModelParams, LLMAdapter } from "./types";
type LLMCompletionParams = {
messages: ChatMessage[];
modelParams: ModelParams;
functionCall?: LLMFunctionCall;
structuredOutputSchema?: ZodSchema;
callbacks?: BaseCallbackHandler[];
baseURL?: string;
apiKey?: string;
@@ -51,7 +45,7 @@ export async function fetchLLMCompletion(
export async function fetchLLMCompletion(
params: LLMCompletionParams & {
streaming: false;
functionCall: LLMFunctionCall;
structuredOutputSchema: ZodSchema;
}
): Promise<unknown>;
@@ -121,18 +115,41 @@ export async function fetchLLMCompletion(
throw new Error("This model provider is not supported.");
}
if (params.functionCall) {
const functionCallingModel = chatModel.bind({
functions: [
{
...params.functionCall,
parameters: zodToJsonSchema(params.functionCall.parameters),
},
],
function_call: { name: params.functionCall.name },
});
const outputParser = new JsonOutputFunctionsParser();
return await functionCallingModel.pipe(outputParser).invoke(finalMessages);
if (params.structuredOutputSchema) {
return await (chatModel as ChatOpenAI) // Typecast necessary due to https://github.com/langchain-ai/langchainjs/issues/6795
.withStructuredOutput(params.structuredOutputSchema)
.invoke(finalMessages);
}
/*
Workaround OpenAI o1 while in beta:
This is a temporary workaround to avoid sending system messages to OpenAI's O1 models.
O1 models do not support in beta:
- system messages
- top_p
- max_tokens at all, one has to use max_completion_tokens instead
- temperature different than 1
Reference: https://platform.openai.com/docs/guides/reasoning/beta-limitations
*/
if (modelParams.model.startsWith("o1-")) {
return await new ChatOpenAI({
openAIApiKey: apiKey,
modelName: modelParams.model,
temperature: 1,
maxTokens: undefined,
topP: undefined,
callbacks,
maxRetries,
configuration: {
baseURL,
},
})
.pipe(new StringOutputParser())
.invoke(
finalMessages.filter((message) => message._getType() !== "system")
);
}
if (streaming) {
+7 -1
View File
@@ -4,7 +4,7 @@ import z from "zod";
export type PromptVariable = { name: string; value: string; isUsed: boolean };
export type ChatMessage = {
role: ChatMessageRole;
role: ChatMessageRole | string; // Users may ingest any string as role via API/SDK
content: string;
};
@@ -22,6 +22,8 @@ export enum ChatMessageRole {
Assistant = "assistant",
}
export const ChatMessageDefaultRoleSchema = z.nativeEnum(ChatMessageRole);
export type ModelParams = {
provider: string;
adapter: LLMAdapter;
@@ -52,6 +54,10 @@ export const openAIModels = [
"gpt-4o-2024-05-13",
"gpt-4o-mini",
"gpt-4o-mini-2024-07-18",
"o1-preview",
"o1-preview-2024-09-12",
"o1-mini",
"o1-mini-2024-09-12",
"gpt-4-turbo-preview",
"gpt-4-1106-preview",
"gpt-4-0613",
+50
View File
@@ -0,0 +1,50 @@
import { env } from "../env";
import winston from "winston";
import { getCurrentSpan } from "./instrumentation";
const tracingFormat = function () {
return winston.format((info) => {
const span = getCurrentSpan();
if (span) {
const { spanId, traceId } = span.spanContext();
const traceIdEnd = traceId.slice(traceId.length / 2);
info["dd.trace_id"] = BigInt(`0x${traceIdEnd}`).toString();
info["dd.span_id"] = BigInt(`0x${spanId}`).toString();
info["trace_id"] = traceId;
info["span_id"] = spanId;
}
return info;
})();
};
const getWinstonLogger = (
nodeEnv: "development" | "production" | "test",
minLevel = "info",
) => {
const textLoggerFormat = winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.timestamp(),
winston.format.align(),
winston.format.printf((info) => {
const logMessage = `${info.timestamp} ${info.level} ${info.message}`;
return info.stack ? `${logMessage}\n${info.stack}` : logMessage;
}),
);
const jsonLoggerFormat = winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.timestamp(),
tracingFormat(),
winston.format.json(),
);
const format =
env.LANGFUSE_LOG_FORMAT === "text" ? textLoggerFormat : jsonLoggerFormat;
return winston.createLogger({
level: minLevel,
format: format,
transports: [new winston.transports.Console()],
});
};
export const logger = getWinstonLogger(env.NODE_ENV, env.LANGFUSE_LOG_LEVEL);
@@ -2,8 +2,9 @@ import { z } from "zod";
import { Prisma } from "@prisma/client";
import type { ColumnDefinition } from "./tableDefinitions/types";
import type { OrderByState } from "./interfaces/orderBy";
import type { ColumnDefinition } from "../tableDefinitions/types";
import type { OrderByState } from "../interfaces/orderBy";
import { logger } from "./logger";
/**
* Convert orderBy to SQL ORDER BY clause
@@ -13,7 +14,7 @@ import type { OrderByState } from "./interfaces/orderBy";
*/
export function orderByToPrismaSql(
orderBy: OrderByState,
tableColumns: ColumnDefinition[]
tableColumns: ColumnDefinition[],
): Prisma.Sql {
if (!orderBy) {
return Prisma.sql`ORDER BY t.timestamp DESC`;
@@ -22,11 +23,11 @@ export function orderByToPrismaSql(
const col = tableColumns.find(
// TODO: Only use id instead of name.
// It's less error-prone & decouples data fetching from the human-readable UI labels
(c) => c.name === orderBy.column || c.id === orderBy.column
(c) => c.name === orderBy.column || c.id === orderBy.column,
);
if (!col) {
console.log("Invalid filter column", orderBy.column);
logger.warn("Invalid filter column", orderBy.column);
throw new Error("Invalid filter column: " + orderBy.column);
}
@@ -34,12 +35,12 @@ export function orderByToPrismaSql(
const orderByOrder = z.enum(["ASC", "DESC"]);
const order = orderByOrder.safeParse(orderBy.order);
if (!order.success) {
console.log("Invalid order", orderBy.order);
logger.warn("Invalid order", orderBy.order);
throw new Error("Invalid order: " + orderBy.order);
}
// Both column and order are safe, can use raw SQL
return Prisma.raw(
`ORDER BY ${col.internal} ${order.data} ${col.nullable ? (orderBy.order === "DESC" ? "NULLS LAST" : "NULLS FIRST") : ""}`
`ORDER BY ${col.internal} ${order.data} ${col.nullable ? (orderBy.order === "DESC" ? "NULLS LAST" : "NULLS FIRST") : ""}`,
);
}
@@ -0,0 +1,172 @@
import { ObservationView, Prisma } from "@prisma/client";
import {
datetimeFilterToPrismaSql,
tableColumnsToSqlFilterAndPrefix,
} from "../filterToPrisma";
import { orderByToPrismaSql } from "../orderByToPrisma";
import { observationsTableCols } from "../../observationsTable";
import { TableFilters } from "./types";
type AdditionalObservationFields = {
traceName: string | null;
promptName: string | null;
promptVersion: string | null;
traceTags: Array<string>;
};
type FullObservation = AdditionalObservationFields & ObservationView;
export type FullObservations = Array<FullObservation>;
export type FullObservationsWithScores = Array<
FullObservation & { scores?: Record<string, string[] | number[]> | null }
>;
export type IOAndMetadataOmittedObservations = Array<
Omit<ObservationView, "input" | "output" | "metadata"> &
AdditionalObservationFields
>;
export function parseGetAllGenerationsInput(filters: TableFilters) {
const searchCondition = filters.searchQuery
? Prisma.sql`AND (
o."id" ILIKE ${`%${filters.searchQuery}%`} OR
o."name" ILIKE ${`%${filters.searchQuery}%`} OR
o."model" ILIKE ${`%${filters.searchQuery}%`} OR
t."name" ILIKE ${`%${filters.searchQuery}%`}
)`
: Prisma.empty;
const filterCondition = tableColumnsToSqlFilterAndPrefix(
filters.filter ?? [],
observationsTableCols,
"observations"
);
const orderByCondition = orderByToPrismaSql(
filters.orderBy,
observationsTableCols
);
// to improve query performance, add timeseries filter to observation queries as well
const startTimeFilter = filters.filter?.find(
(f) => f.column === "Start Time" && f.type === "datetime"
);
const datetimeFilter =
startTimeFilter && startTimeFilter.type === "datetime"
? datetimeFilterToPrismaSql(
"start_time",
startTimeFilter.operator,
startTimeFilter.value
)
: Prisma.empty;
return {
searchCondition,
filterCondition,
orderByCondition,
datetimeFilter,
};
}
export function createGenerationsQuery({
projectId,
datetimeFilter = Prisma.empty,
page,
limit,
searchCondition = Prisma.empty,
filterCondition = Prisma.empty,
orderByCondition = Prisma.empty,
selectIOAndMetadata = false,
selectScoreValues = false,
}: {
projectId: string;
datetimeFilter?: Prisma.Sql;
page?: number;
limit?: number;
searchCondition?: Prisma.Sql;
filterCondition?: Prisma.Sql;
orderByCondition?: Prisma.Sql;
selectIOAndMetadata?: boolean;
selectScoreValues?: boolean;
}) {
return Prisma.sql`
WITH scores_avg AS (
SELECT
trace_id,
observation_id,
${selectScoreValues ? Prisma.sql`jsonb_object_agg(name::text, "values") AS "scores_values",` : Prisma.empty}
jsonb_object_agg(name::text, avg_value::double precision) AS "scores_avg"
FROM (
SELECT
trace_id,
observation_id,
name,
${selectScoreValues ? Prisma.sql`array_agg(COALESCE(string_value, value::text)) AS "values",` : Prisma.empty}
avg(value) avg_value,
comment
FROM
scores
WHERE
project_id = ${projectId}
${selectScoreValues ? Prisma.empty : Prisma.sql`AND scores."data_type" IN ('NUMERIC', 'BOOLEAN')`}
GROUP BY
trace_id,
observation_id,
name,
comment
ORDER BY
trace_id
) tmp
GROUP BY
trace_id,
observation_id
)
SELECT
${selectScoreValues ? Prisma.sql`s_avg."scores_values" AS "scores",` : Prisma.empty}
o.id,
o.name,
o.model,
o."modelParameters",
o.start_time as "startTime",
o.end_time as "endTime",
${selectIOAndMetadata ? Prisma.sql`o.input, o.output, o.metadata,` : Prisma.empty}
o.trace_id as "traceId",
t.name as "traceName",
o.completion_start_time as "completionStartTime",
o.time_to_first_token as "timeToFirstToken",
o.prompt_tokens as "promptTokens",
o.completion_tokens as "completionTokens",
o.total_tokens as "totalTokens",
o.unit,
o.level,
o.status_message as "statusMessage",
o.version,
o.model_id as "modelId",
o.input_price as "inputPrice",
o.output_price as "outputPrice",
o.total_price as "totalPrice",
o.calculated_input_cost as "calculatedInputCost",
o.calculated_output_cost as "calculatedOutputCost",
o.calculated_total_cost as "calculatedTotalCost",
o."latency",
o.prompt_id as "promptId",
p.name as "promptName",
p.version as "promptVersion",
t.tags as "traceTags"
FROM observations_view o
JOIN traces t ON t.id = o.trace_id AND t.project_id = ${projectId}
LEFT JOIN scores_avg AS s_avg ON s_avg.trace_id = t.id and s_avg.observation_id = o.id
LEFT JOIN prompts p ON p.id = o.prompt_id AND p.project_id = ${projectId}
WHERE
o.project_id = ${projectId}
AND o.type = 'GENERATION'
${datetimeFilter}
${searchCondition}
${filterCondition}
${orderByCondition}
${limit ? Prisma.sql`LIMIT ${limit}` : Prisma.empty}
${page && limit ? Prisma.sql`OFFSET ${page * limit}` : Prisma.empty}
`;
}
@@ -2,10 +2,10 @@ import { z } from "zod";
import { Prisma } from "@prisma/client";
import { tableColumnsToSqlFilterAndPrefix } from "../filterToPrisma";
import { singleFilter } from "../interfaces/filters";
import { orderBy } from "../interfaces/orderBy";
import { singleFilter } from "../../interfaces/filters";
import { orderBy } from "../../interfaces/orderBy";
import { orderByToPrismaSql } from "../orderByToPrisma";
import { sessionsViewCols } from "../tableDefinitions/index";
import { sessionsViewCols } from "../../tableDefinitions";
const GetSessionTableSQLParamsSchema = z.object({
projectId: z.string(),
@@ -22,7 +22,7 @@ export const createSessionsAllQuery = (
options?: {
ignoreOrderBy?: boolean; // used by session.metrics and session.all.totalCount
sessionIdList?: string[]; // used by session.metrics
}
},
): Prisma.Sql => {
const { projectId, filter, orderBy, page, limit } =
GetSessionTableSQLParamsSchema.parse(params);
@@ -30,7 +30,7 @@ export const createSessionsAllQuery = (
const filterCondition = tableColumnsToSqlFilterAndPrefix(
filter ?? [],
sessionsViewCols,
"sessions"
"sessions",
);
const orderByCondition = orderByToPrismaSql(orderBy, sessionsViewCols);
@@ -0,0 +1,135 @@
import { Prisma } from "@prisma/client";
import { TableFilters } from "./types";
import {
datetimeFilterToPrismaSql,
tableColumnsToSqlFilterAndPrefix,
} from "../filterToPrisma";
import { tracesTableCols } from "../../tracesTable";
import { orderByToPrismaSql } from "../orderByToPrisma";
export function parseTraceAllFilters(input: TableFilters) {
const filterCondition = tableColumnsToSqlFilterAndPrefix(
input.filter ?? [],
tracesTableCols,
"traces"
);
const orderByCondition = orderByToPrismaSql(input.orderBy, tracesTableCols);
// to improve query performance, add timeseries filter to observation queries as well
const timeseriesFilter = input.filter?.find(
(f) => f.column === "Timestamp" && f.type === "datetime"
);
const observationTimeseriesFilter =
timeseriesFilter && timeseriesFilter.type === "datetime"
? datetimeFilterToPrismaSql(
"start_time",
timeseriesFilter.operator,
timeseriesFilter.value
)
: Prisma.empty;
const searchCondition = input.searchQuery
? Prisma.sql`AND (
t."id" ILIKE ${`%${input.searchQuery}%`} OR
t."external_id" ILIKE ${`%${input.searchQuery}%`} OR
t."user_id" ILIKE ${`%${input.searchQuery}%`} OR
t."name" ILIKE ${`%${input.searchQuery}%`}
)`
: Prisma.empty;
return {
filterCondition,
orderByCondition,
observationTimeseriesFilter,
searchCondition,
};
}
export function createTracesQuery({
select,
projectId,
observationTimeseriesFilter = Prisma.empty,
page,
limit,
searchCondition = Prisma.empty,
filterCondition = Prisma.empty,
orderByCondition = Prisma.empty,
selectScoreValues = false,
}: {
select: Prisma.Sql;
projectId: string;
observationTimeseriesFilter?: Prisma.Sql;
page?: number;
limit?: number;
searchCondition?: Prisma.Sql;
filterCondition?: Prisma.Sql;
orderByCondition?: Prisma.Sql;
selectScoreValues?: boolean;
}) {
return Prisma.sql`
SELECT
${select}
FROM
"traces" AS t
LEFT JOIN LATERAL (
SELECT
SUM(prompt_tokens) AS "promptTokens",
SUM(completion_tokens) AS "completionTokens",
SUM(total_tokens) AS "totalTokens",
SUM(calculated_total_cost) AS "calculatedTotalCost",
SUM(calculated_input_cost) AS "calculatedInputCost",
SUM(calculated_output_cost) AS "calculatedOutputCost"
FROM
"observations_view"
WHERE
trace_id = t.id
AND "type" = 'GENERATION'
AND "project_id" = ${projectId}
${observationTimeseriesFilter}
) AS generation_metrics ON true
LEFT JOIN LATERAL (
SELECT
COUNT(*) AS "observationCount",
EXTRACT(EPOCH FROM COALESCE(MAX("end_time"), MAX("start_time"))) - EXTRACT(EPOCH FROM MIN("start_time"))::double precision AS "latency",
COALESCE(
MAX(CASE WHEN level = 'ERROR' THEN 'ERROR' END),
MAX(CASE WHEN level = 'WARNING' THEN 'WARNING' END),
MAX(CASE WHEN level = 'DEFAULT' THEN 'DEFAULT' END),
'DEBUG'
) AS "level"
FROM
"observations"
WHERE
trace_id = t.id
AND "project_id" = ${projectId}
${observationTimeseriesFilter}
) AS observation_metrics ON true
LEFT JOIN LATERAL (
SELECT
${selectScoreValues ? Prisma.sql`jsonb_object_agg(name::text, "values") AS "scores_values",` : Prisma.empty}
jsonb_object_agg(name::text, avg_value::double precision) AS "scores_avg"
FROM (
SELECT
name,
${selectScoreValues ? Prisma.sql`array_agg(COALESCE(string_value, value::text)) AS "values",` : Prisma.empty}
AVG(value) avg_value
FROM
scores
WHERE
trace_id = t.id
AND t."project_id" = ${projectId}
${selectScoreValues ? Prisma.empty : Prisma.sql`AND scores."data_type" IN ('NUMERIC', 'BOOLEAN')`}
GROUP BY
name
) tmp
) AS s_avg ON true
WHERE
t."project_id" = ${projectId}
${searchCondition}
${filterCondition}
${orderByCondition}
${limit ? Prisma.sql`LIMIT ${limit}` : Prisma.empty}
${page && limit ? Prisma.sql`OFFSET ${page * limit}` : Prisma.empty}
`;
}
@@ -0,0 +1,9 @@
export { createSessionsAllQuery } from "./createSessionsAllQuery";
export { createTracesQuery, parseTraceAllFilters } from "./createTracesQuery";
export {
createGenerationsQuery,
parseGetAllGenerationsInput,
type FullObservations,
type FullObservationsWithScores,
type IOAndMetadataOmittedObservations,
} from "./createGenerationsQuery";
@@ -0,0 +1,14 @@
import z from "zod";
import { singleFilter } from "../../interfaces/filters";
import { orderBy } from "../../interfaces/orderBy";
import { optionalPaginationZod } from "../../utils/zod";
const TableFilterSchema = z.object({
projectId: z.string(),
filter: z.array(singleFilter).nullish(),
searchQuery: z.string().nullish(),
orderBy: orderBy,
...optionalPaginationZod,
});
export type TableFilters = z.infer<typeof TableFilterSchema>;
+31 -2
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import { ingestionBatchEvent } from ".";
import { eventTypes, ingestionBatchEvent, TCarrier } from ".";
export enum EventName {
TraceUpsert = "TraceUpsert",
@@ -9,7 +9,8 @@ export enum EventName {
CloudUsageMetering = "CloudUsageMetering",
}
export const LegacyIngestionEvent = z.object({
export const LegacyIngestionEventFull = z.object({
useS3EventStore: z.literal(false),
data: ingestionBatchEvent,
authCheck: z.object({
validKey: z.literal(true),
@@ -20,6 +21,29 @@ export const LegacyIngestionEvent = z.object({
}),
});
export const LegacyIngestionEventMeta = z.object({
useS3EventStore: z.literal(true),
data: z.array(
z.object({
type: z.nativeEnum(eventTypes),
eventBodyId: z.string(),
eventId: z.string(),
})
),
authCheck: z.object({
validKey: z.literal(true),
scope: z.object({
projectId: z.string(),
accessLevel: z.enum(["all", "scores"]),
}),
}),
});
export const LegacyIngestionEvent = z.discriminatedUnion("useS3EventStore", [
LegacyIngestionEventFull,
LegacyIngestionEventMeta,
]);
export const BatchExportJobSchema = z.object({
projectId: z.string(),
batchExportId: z.string(),
@@ -31,6 +55,7 @@ export const TraceUpsertEventSchema = z.object({
export const EvalExecutionEvent = z.object({
projectId: z.string(),
jobExecutionId: z.string(),
delay: z.number().nullish(),
});
export type BatchExportJobType = z.infer<typeof BatchExportJobSchema>;
@@ -80,23 +105,27 @@ export type TQueueJobTypes = {
id: string;
payload: TraceUpsertEventType;
name: QueueJobs.TraceUpsert;
_tracecontext?: TCarrier;
};
[QueueName.EvaluationExecution]: {
timestamp: Date;
id: string;
payload: EvalExecutionEventType;
name: QueueJobs.EvaluationExecution;
_tracecontext?: TCarrier;
};
[QueueName.BatchExport]: {
timestamp: Date;
id: string;
payload: BatchExportJobType;
name: QueueJobs.BatchExportJob;
_tracecontext?: TCarrier;
};
[QueueName.LegacyIngestionQueue]: {
timestamp: Date;
id: string;
payload: LegacyIngestionEventType;
name: QueueJobs.LegacyIngestionJob;
_tracecontext?: TCarrier;
};
};
@@ -15,8 +15,12 @@ export const getBatchExportQueue = () => {
connection: connection,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100,
removeOnFail: 10_000,
attempts: 2,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
@@ -18,9 +18,13 @@ export const getIngestionFlushQueue = () => {
connection: connection,
defaultJobOptions: {
removeOnComplete: true, // Important: If not true, new jobs for that ID would be ignored as jobs in the complete set are still considered as part of the queue
removeOnFail: 1_000,
removeOnFail: 100_000,
delay: env.LANGFUSE_INGESTION_FLUSH_DELAY_MS,
attempts: env.LANGFUSE_INGESTION_FLUSH_ATTEMPTS,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
@@ -21,8 +21,12 @@ export class LegacyIngestionQueue {
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100,
removeOnFail: 100_000,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
},
)
+2 -1
View File
@@ -1,5 +1,6 @@
import Redis, { RedisOptions } from "ioredis";
import { env } from "../../env";
import { logger } from "../logger";
export const createNewRedisInstance = (
additionalOptions: Partial<RedisOptions> = {},
@@ -26,7 +27,7 @@ const createRedisClient = () => {
try {
return createNewRedisInstance();
} catch (e) {
console.error(e, "Failed to connect to redis");
logger.error("Failed to connect to redis", e);
return null;
}
};
@@ -21,7 +21,12 @@ export const getTraceUpsertQueue = () => {
connection: connection,
defaultJobOptions: {
removeOnComplete: 100, // Important: If not true, new jobs for that ID would be ignored as jobs in the complete set are still considered as part of the queue
removeOnFail: 1_000,
removeOnFail: 100_000,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
@@ -30,7 +35,7 @@ export const getTraceUpsertQueue = () => {
};
export function convertTraceUpsertEventsToRedisEvents(
events: TraceUpsertEventType[]
events: TraceUpsertEventType[],
) {
const uniqueTracesPerProject = events.reduce((acc, event) => {
if (!acc.get(event.projectId)) {
@@ -1,6 +1,7 @@
import { Prompt, PrismaClient } from "@prisma/client";
import { Redis } from "ioredis";
import { env } from "../../env";
import { logger } from "../logger";
export class PromptService {
private cacheEnabled: boolean;
@@ -11,7 +12,7 @@ export class PromptService {
private redis: Redis | null,
private metricIncrementer?: // used for otel metrics
(name: string, value?: number) => void,
cacheEnabled?: boolean // used for testing
cacheEnabled?: boolean, // used for testing
) {
this.cacheEnabled =
Boolean(redis) &&
@@ -25,7 +26,7 @@ export class PromptService {
const cachedPrompt = await this.getCachedPrompt(params);
this.incrementMetric(
cachedPrompt ? Metrics.PromptCacheHit : Metrics.PromptCacheMiss
cachedPrompt ? Metrics.PromptCacheHit : Metrics.PromptCacheMiss,
);
if (cachedPrompt) {
@@ -117,7 +118,7 @@ export class PromptService {
}
public async lockCache(
params: Pick<PromptParams, "projectId" | "promptName">
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<void> {
if (!this.cacheEnabled) return;
@@ -133,7 +134,7 @@ export class PromptService {
}
public async unlockCache(
params: Pick<PromptParams, "projectId" | "promptName">
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<void> {
if (!this.cacheEnabled) return;
@@ -149,7 +150,7 @@ export class PromptService {
}
private async isCacheLocked(
params: Pick<PromptParams, "projectId" | "promptName">
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<boolean> {
const lockKey = this.getLockKey(params);
@@ -163,14 +164,14 @@ export class PromptService {
}
private getLockKey(
params: Pick<PromptParams, "projectId" | "promptName">
params: Pick<PromptParams, "projectId" | "promptName">,
): string {
// Important to *pre*fix LOCK as otherwise it would be deleted by deleteKeysByPrefix
return `LOCK:${this.getCacheKeyPrefix(params)}`;
}
public async invalidateCache(
params: Pick<PromptParams, "projectId" | "promptName">
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<void> {
if (!this.cacheEnabled) return;
@@ -187,7 +188,7 @@ export class PromptService {
await this.redis?.del([...(keys ?? []), keyIndexKey]);
this.logInfo(
`Cache invalidated for prefix ${cacheKeyPrefix} in ${Date.now() - startTime}ms`
`Cache invalidated for prefix ${cacheKeyPrefix} in ${Date.now() - startTime}ms`,
);
} catch (e) {
this.logError("Error deleting keys for prefix", cacheKeyPrefix, e);
@@ -203,23 +204,23 @@ export class PromptService {
}
private getCacheKeyPrefix(
params: Pick<PromptParams, "projectId" | "promptName">
params: Pick<PromptParams, "projectId" | "promptName">,
): string {
return `prompt:${params.projectId}:${params.promptName}`;
}
private getKeyIndexKey(
params: Pick<PromptParams, "projectId" | "promptName">
params: Pick<PromptParams, "projectId" | "promptName">,
): string {
return `prompt_key_index:${params.projectId}:${params.promptName}`;
}
private logError(message: string, ...args: any[]) {
console.error(`[PromptService] ${message}`, ...args);
logger.error(`[PromptService] ${message}`, ...args);
}
private logInfo(message: string, ...args: any[]) {
console.log(`[PromptService] ${message}`, ...args);
logger.info(`[PromptService] ${message}`, ...args);
}
private incrementMetric(name: Metrics, value: number = 1) {
@@ -1,7 +1,12 @@
import type { Readable } from "stream";
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
import {
GetObjectCommand,
PutObjectCommand,
S3Client,
} from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { logger } from "../logger";
type UploadFile = {
fileName: string;
@@ -15,24 +20,30 @@ export class S3StorageService {
private bucketName: string;
constructor(params: {
accessKeyId: string;
secretAccessKey: string;
accessKeyId: string | undefined;
secretAccessKey: string | undefined;
bucketName: string;
endpoint: string;
region: string;
endpoint: string | undefined;
region: string | undefined;
forcePathStyle: boolean;
}) {
const { accessKeyId, secretAccessKey, bucketName, endpoint, region } =
params;
// Use accessKeyId and secretAccessKey if provided or fallback to default credentials
const { accessKeyId, secretAccessKey } = params;
const credentials =
accessKeyId !== undefined && secretAccessKey !== undefined
? {
accessKeyId,
secretAccessKey,
}
: undefined;
this.client = new S3Client({
credentials: {
accessKeyId,
secretAccessKey,
},
endpoint,
region,
credentials,
endpoint: params.endpoint,
region: params.region,
forcePathStyle: params.forcePathStyle,
});
this.bucketName = bucketName;
this.bucketName = params.bucketName;
}
public async uploadFile({
@@ -56,15 +67,45 @@ export class S3StorageService {
return { signedUrl };
} catch (err) {
console.error(err);
logger.error(`Failed to upload file to ${fileName}`, err);
throw new Error("Failed to upload to S3 or generate signed URL");
}
}
public async uploadJson(path: string, body: Record<string, unknown>[]) {
const putCommand = new PutObjectCommand({
Bucket: this.bucketName,
Key: path,
Body: JSON.stringify(body),
ContentType: "application/json",
});
try {
await this.client.send(putCommand);
} catch (err) {
logger.error(`Failed to upload JSON to S3 ${path}`, err);
throw Error("Failed to upload JSON to S3");
}
}
public async download(path: string): Promise<string> {
const getCommand = new GetObjectCommand({
Bucket: this.bucketName,
Key: path,
});
try {
const response = await this.client.send(getCommand);
return (await response.Body?.transformToString()) ?? "";
} catch (err) {
logger.error(`Failed to download file from S3 ${path}`, err);
throw Error("Failed to download file from S3");
}
}
private async getSignedUrl(
fileName: string,
ttlSeconds: number
ttlSeconds: number,
): Promise<string> {
try {
return await getSignedUrl(
@@ -74,9 +115,10 @@ export class S3StorageService {
Key: fileName,
ResponseContentDisposition: `attachment; filename="${fileName}"`,
}),
{ expiresIn: ttlSeconds }
{ expiresIn: ttlSeconds },
);
} catch (err) {
logger.error(`Failed to generate presigned URL for ${fileName}`, err);
throw Error("Failed to generate signed URL");
}
}
@@ -19,7 +19,6 @@ interface BatchExportSuccessTemplateProps {
receiverEmail: string;
downloadLink: string;
batchExportName: string;
expiresInHours: number;
}
export const BatchExportSuccessEmailTemplate = ({
@@ -27,7 +26,6 @@ export const BatchExportSuccessEmailTemplate = ({
downloadLink,
userName,
batchExportName,
expiresInHours,
}: BatchExportSuccessTemplateProps) => {
const previewText = `Download your data export from Langfuse`;
@@ -56,8 +54,11 @@ export const BatchExportSuccessEmailTemplate = ({
<Text className="text-sm leading-6 text-black">
Your data export{" "}
<span className="font-mono">{batchExportName}</span> is ready to
download. The download link will expire in{" "}
<strong>{expiresInHours} hours</strong>.
download. The download link is valid for a few hours.
</Text>
<Text className="text-sm leading-6 text-black">
Please note data exports do not reflect custom column ordering or
visibility.
</Text>
<Section className="mb-4 mt-8 text-center">
<Button
@@ -3,6 +3,7 @@ import { parseConnectionUrl } from "nodemailer/lib/shared/index.js";
import { render } from "@react-email/render";
import { BatchExportSuccessEmailTemplate } from "./BatchExportSuccessEmailTemplate";
import { logger } from "../../../logger";
type SendBatchExportSuccessParams = {
env: Partial<
@@ -12,7 +13,6 @@ type SendBatchExportSuccessParams = {
downloadLink: string;
userName: string;
batchExportName: string;
expiresInHours: number;
};
export const sendBatchExportSuccessEmail = async ({
@@ -21,11 +21,9 @@ export const sendBatchExportSuccessEmail = async ({
downloadLink,
userName,
batchExportName,
expiresInHours,
}: SendBatchExportSuccessParams) => {
if (!env.EMAIL_FROM_ADDRESS || !env.SMTP_CONNECTION_URL) {
console.error("Missing environment variables for sending email.");
logger.error("Missing environment variables for sending email.");
return;
}
@@ -37,7 +35,6 @@ export const sendBatchExportSuccessEmail = async ({
downloadLink,
userName,
batchExportName,
expiresInHours,
})
);
@@ -51,6 +48,6 @@ export const sendBatchExportSuccessEmail = async ({
html: htmlTemplate,
});
} catch (error) {
console.error(error);
logger.error(error);
}
};
@@ -3,6 +3,7 @@ import { parseConnectionUrl } from "nodemailer/lib/shared/index.js";
import { render } from "@react-email/render";
import MembershipInvitationTemplate from "./MembershipInvitationEmailTemplate";
import { logger } from "../../../logger";
const langfuseUrls = {
US: "https://us.cloud.langfuse.com",
@@ -34,7 +35,7 @@ export const sendMembershipInvitationEmail = async ({
orgName,
}: SendMembershipInvitationParams) => {
if (!env.EMAIL_FROM_ADDRESS || !env.SMTP_CONNECTION_URL) {
console.error(
logger.error(
"Missing environment variables for sending membership invitation email."
);
return;
@@ -49,7 +50,7 @@ export const sendMembershipInvitationEmail = async ({
const authUrl = getAuthURL();
if (!authUrl) {
console.error(
logger.error(
"Missing NEXTAUTH_URL or NEXT_PUBLIC_LANGFUSE_CLOUD_REGION environment variable."
);
return;
@@ -77,6 +78,6 @@ export const sendMembershipInvitationEmail = async ({
html: htmlTemplate,
});
} catch (error) {
console.error(error);
logger.error(error);
}
};
@@ -56,8 +56,9 @@ const ResetPasswordTemplate = ({ url }: ResetPasswordTemplateProps) => {
</Button>
</Section>
<Text className="text-center text-xs leading-6 text-[#666666]">
If you do not want to change your password or didn&apos;t request
a reset, you can ignore and delete this email.
The link is valid for 10 minutes. If you do not want to change
your password or didn&apos;t request a reset, you can ignore and
delete this email.
</Text>
</Container>
</Body>
@@ -76,7 +77,7 @@ export async function sendResetPasswordVerificationRequest(
to: identifier,
from: provider.from,
subject: `Forgot your password?`,
text: `To reset your Langfuse password, please confirm your email:\n${url}\n\nIf you do not want to change your password or didn't request a reset, you can ignore and delete this email.`,
text: `To reset your Langfuse password, please confirm your email:\n${url}\n\nThe link is valid for 10 minutes. If you do not want to change your password or didn't request a reset, you can ignore and delete this email.`,
html: htmlTemplate,
});
const failed = result.rejected.concat(result.pending).filter(Boolean);
@@ -1,6 +1,8 @@
import { Transform, type TransformCallback } from "stream";
import { stringify } from "./stringify";
const DELIMITER = ",";
export function transformStreamToCsv(): Transform {
let isFirstChunk = true;
let headers: string[] = [];
@@ -15,15 +17,19 @@ export function transformStreamToCsv(): Transform {
if (isFirstChunk) {
// Extract headers from the first object
headers = Object.keys(row);
this.push(headers.join(",") + "\n");
this.push(headers.join(DELIMITER) + "\n");
isFirstChunk = false;
}
// Convert the object to a CSV line and push it
const csvRow = headers.map((header) => {
const field = row[header];
const str = stringify(field);
let str = stringify(field);
// escape and format fields that contain commas
if (str.includes(",")) {
str = `"${str.replace(/"/g, '""')}"`;
}
if (str.startsWith('"') && str.endsWith('"')) {
return str;
} else {
@@ -31,7 +37,7 @@ export function transformStreamToCsv(): Transform {
}
});
this.push(csvRow.join(",") + "\n");
this.push(csvRow.join(DELIMITER) + "\n");
callback();
},
+5 -5
View File
@@ -78,28 +78,28 @@ export const tracesTableCols: ColumnDefinition[] = [
name: "Input Tokens",
id: "inputTokens",
type: "number",
internal: 'tm."promptTokens"',
internal: 'generation_metrics."promptTokens"',
nullable: true,
},
{
name: "Output Tokens",
id: "outputTokens",
type: "number",
internal: 'tm."completionTokens"',
internal: 'generation_metrics."completionTokens"',
nullable: true,
},
{
name: "Total Tokens",
id: "totalTokens",
type: "number",
internal: 'tm."totalTokens"',
internal: 'generation_metrics."totalTokens"',
nullable: true,
},
{
name: "Usage",
id: "usage",
type: "number",
internal: 'tm."totalTokens"',
internal: 'generation_metrics."totalTokens"',
nullable: true,
},
@@ -113,7 +113,7 @@ export const tracesTableCols: ColumnDefinition[] = [
name: "Latency (s)",
id: "latency",
type: "number",
internal: "tl.latency",
internal: "observation_metrics.latency",
},
{
name: "Input Cost ($)",
+2 -1
View File
@@ -1,7 +1,8 @@
import { type z } from "zod";
import { singleFilter } from "./interfaces/filters";
import { singleFilter, timeFilter } from "./interfaces/filters";
// to be sent to the server
export type TimeFilter = z.infer<typeof timeFilter>;
export type FilterCondition = z.infer<typeof singleFilter>;
export type FilterState = FilterCondition[];
+10
View File
@@ -0,0 +1,10 @@
// https://github.com/t3-oss/t3-env/blob/e7e21095e00a477e37608783defda5a6a99586d0/packages/core/src/index.ts#L228
// unfortunately, we are not able to install t3-env in all our packaging as some rely on commonjs.
export const removeEmptyEnvVariables = (runtimeEnv: any) => {
for (const [key, value] of Object.entries(runtimeEnv)) {
if (value === "") {
delete runtimeEnv[key];
}
}
return runtimeEnv;
};

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