Compare commits

...
307 Commits
Author SHA1 Message Date
Marc Klingen 485120806e chore: release v3.3.0 2024-12-19 22:02:50 +01:00
87acc4f7f3 feat: redirect ingestion traffic for specific projects to secondary queue (#4792)
* feat: redirect specific projects to secondary ingestion queue

* feat: redirect specific projects to secondary ingestion queue

* push

* Update worker/src/queues/ingestionQueue.ts

Co-authored-by: Steffen Schmitz <steffen@langfuse.com>

* push

* push

---------

Co-authored-by: Steffen Schmitz <steffen@langfuse.com>
2024-12-19 16:06:20 +00:00
Steffen SchmitzandGitHub 21b1faa21b fix: adjust clickhouse type mapping for session duration filter (#4791) 2024-12-19 13:04:39 +00:00
Max DeichmannandGitHub 76cfaf1069 chore: measure lookup hits in ingestion pipeline (#4787) 2024-12-19 10:20:10 +01:00
dbe808d520 feat(auth): add AUTH_IGNORE_ACCOUNT_FIELDS to sanitize IDP fields before creating an account (#4728)
* feat: Field sanitization before creating an Account

* add comments

---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-12-18 23:26:56 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6c977429c2 chore(deps-dev): bump tailwindcss from 3.4.13 to 3.4.17 (#4784)
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 3.4.13 to 3.4.17.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.17/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.13...v3.4.17)

---
updated-dependencies:
- dependency-name: tailwindcss
  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-12-18 23:24:43 +00:00
Marc KlingenandGitHub c42c6a0f91 chore: do not add latest, major, and major.minor tags to docker images of -rc versions (#4781) 2024-12-18 21:37:46 +01:00
Max DeichmannandGitHub f14b69f68d chore: add attributes to ingestion queue processing (#4779)
git push
2024-12-18 19:54:32 +00:00
marliessophieandGitHub dd892459a4 chore(ui): order score names alphabetically in dropdowns (#4776)
* chore(ui): order score names alphabetically in dropdowns

* push
2024-12-18 16:40:04 +00:00
Marc KlingenandGitHub 3488b5c443 docs: Update README.md 2024-12-18 03:51:09 +01:00
Max DeichmannandGitHub 6e0f4483b4 chore: increase rate limits (#4751) 2024-12-17 15:55:18 +00:00
Marc KlingenandGitHub b5d9fb3e63 chore(cloud): remove posthog metrics cron job (#4753) 2024-12-17 15:52:42 +00:00
Marc KlingenandGitHub e087da6b6e docs: update README.md 2024-12-17 12:53:21 +01:00
Marc KlingenandGitHub ea8734d709 docs: update readme (#4749) 2024-12-17 12:49:14 +01:00
Hassieb PakzadandGitHub 9cf4e57da1 fix(dashboard-modelUsage): none model name handling (#4748) 2024-12-17 10:52:36 +01:00
Marc KlingenandGitHub 292501b6dd feat(ui): add organizations menu item link for all global pages (#4741) 2024-12-16 20:46:57 +00:00
Hassieb PakzadandGitHub 2a1424e4d0 fix(dashboard-modelUsage): NaN handling on totals (#4738) 2024-12-16 18:28:19 +01:00
steffen911 fd37e2c35e chore: release v3.2.0 2024-12-16 18:04:14 +01:00
marliessophieandGitHub 0352e7fad6 chore(annotation): return previous score props in delete method (#4737)
chore: return previous annotation score in delete method
2024-12-16 16:47:37 +00:00
Hassieb PakzadandGitHub 6c4482ff56 feat(cost-tracking): allow additional cost and usage keys (#4536) 2024-12-16 17:30:24 +01:00
marliessophieandGitHub cc62b32b82 fix(annotation): fail postgres upsert, delete silentley while Clickhouse is in lead (#4733)
* fix(annotation): fail postgres upsert, delete silentley while Clickhouse is in lead

* chore: disable deletion of score while update operation is in progress
2024-12-16 14:40:04 +00:00
Steffen SchmitzandGitHub b421c2dec5 feat: add posthog integration processing (#4685)
* feat: add posthog integration processing

* chore: add times for query optimization

* chore: cleanups

* chore: remove unused join

* chore: run posthog integration every hour

* chore: rename to langfuse_event_version

* chore: add date workaround for initial sync

* chore: query updates

* chore: remove scheduling info
2024-12-16 13:43:07 +00:00
Marc KlingenandGitHub 3a67533ac8 feat(ui): new experiment modal to highlight prompt and custom experiments (#4731)
* feat(ui): new experiment modal to highlight prompt and custom experiments

* push

* fix lint

* fix lint
2024-12-16 12:00:22 +00:00
steffen911 21303a275a chore: release v3.1.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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-12-16 11:03:21 +01:00
Steffen SchmitzandGitHub 284494c62a fix: use correct golang-migrate in build (#4729)
* fix: use correct golang-migrate in build

* chore: skip docker push

* chore: run docker build right away

* chore: add args for targetplatform and buildplatform

* chore: add TARGETPLATFORM args
2024-12-16 09:44:58 +00:00
Marc KlingenandGitHub b37ec03483 fix(ui): redirect after org/project deletion when using custom base path (#4727) 2024-12-16 09:18:53 +00:00
Steffen SchmitzandGitHub a49a410a04 chore: remove filterlist console log (#4718) 2024-12-13 15:48:09 +00:00
Steffen SchmitzandGitHub 1e9d8de305 fix: rate limit deletions globally to 1 per 3s (#4715) 2024-12-13 10:09:53 +00:00
Marc KlingenandGitHub 0fa88aea57 feat: in HF Spaces, prompt opening in new tab when running in iframe (#4713) 2024-12-13 02:41:37 +01:00
Max DeichmannandGitHub 61240d7c92 fix: fix pagination on public api (#4710)
* fix: fix pagination

* fix: fix pagination

* fix: fix pagination

* fix: fix pagination

* fix: fix pagination

* fix: fix pagination
2024-12-12 23:02:46 +00:00
marliessophieandGitHub d54c888cd9 chore(evals/experiments): support retries via bullmq; set 2 minute LLM call completion timeouts (#4709)
* chore(eval/experiments): support job retries via bullmq instead of within worker

* chore: timeout model completion calls after 2 minutes

* chore: set retries on llm completion calls to 1

* chore: do not propogate expected error cases in experiment creation

* eslint
2024-12-12 20:53:56 +00:00
Max DeichmannandGitHub b87a00fa03 fix: do not fetch dataset run items which are not active (#4708)
* fix

* fix

* push

* fix
2024-12-12 20:31:43 +00:00
marliessophieandGitHub 8b8d1086e5 fix(organizations): block org deletion in case of active projects (#4707)
* fix(organizations): block org deletion in case of active projects

* chore: count soft and hard deleted projects as
2024-12-12 19:23:27 +00:00
Max DeichmannandGitHub d88902316b fix: fix level filter on trace upsert queue (#4704) 2024-12-12 15:40:47 +01:00
Steffen SchmitzandGitHub 5dd5adf850 fix: use clickhouse for failIfRecentEvent condition in healthcheck (#4701) 2024-12-12 12:22:19 +00:00
Max Deichmann a56246059c chore: release v3.1.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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-12-12 13:34:26 +01:00
Max DeichmannandGitHub 833f4165ce chore: improve naming for observations by level chart (#4700)
* feat: add observations status chart

* fix: improve naming for observations by level chart
2024-12-12 12:20:18 +00:00
Max DeichmannandGitHub ebe0c034fd security: upgrade next-auth (#4690)
* upgrade next-auth

* push

* eval execution time
2024-12-12 12:14:33 +00:00
Max DeichmannandGitHub 64492f3002 feat: add observations status chart (#4697) 2024-12-12 12:56:53 +01:00
Max DeichmannandGitHub 27faebe244 chore: improve trace export tests (#4694) 2024-12-12 10:15:37 +00:00
Marc KlingenandGitHub 5e681e5a6d feat(ui): add editable note for each api key (#4686)
* feat(ui): add editable note for each api key

* fix access scopes
2024-12-11 16:51:17 +00:00
marliessophieandGitHub 8af88a0cec fix(playground): Model side menu in playground not scrollable (#4687) 2024-12-11 16:48:18 +00:00
marliessophieandGitHub 1770579fa6 feat(annotation): optimistic updates on annotation form (#3859)
* wip: optimistic updates on annotation form

* refactor to handle sequence of create, update operations correctly

* fix: don't check postgres for score existence when running on clickhouse

* disable annotation button while score updates are saving

* fix: await for all mutations to complete before opening modal again

* rm console log

* fix: update api response clickhouse
2024-12-11 12:39:05 +00:00
Max DeichmannandGitHub 273d29c383 fix: export i/o for traces (#4673) 2024-12-10 16:21:08 +01:00
marliessophieandGitHub 0047a3dec6 fix(experiments): don't require name in experiment creation (#4659)
* fix(experiments): don't require name in experiment creation

* push

* push

* push

* push
2024-12-10 15:00:15 +00:00
Hassieb PakzadandGitHub b63aeeeae7 fix(vertex): at least 1 user message always (#4666) 2024-12-10 10:46:17 +01:00
Marc KlingenandGitHub f5dc7eea07 docs: update upgrade guide url in entrypoint (#4655) 2024-12-09 13:36:27 +00:00
Steffen SchmitzandGitHub 8560a10e53 fix: handle non-existent projects gracefully on delete (#4654)
* fix: handle non-existent projects gracefully on delete

* chore: return early on project does not exist
2024-12-09 13:14:20 +00:00
Marc KlingenandGitHub 609a330c37 chore: update default docker compose to use 3 docker tag (#4653) 2024-12-09 13:31:58 +01:00
Hassieb PakzadandGitHub 07ad7e54e3 fix(model-form): increase max token limit to 16384 (#4651) 2024-12-09 11:52:18 +01:00
Max DeichmannandGitHub 4781b91e90 fix: always restart all docker services (#4649)
fix
2024-12-09 10:10:27 +00:00
marliessophieandGitHub 2c68223402 fix(experiments): ensure experiment runs on all valid inputs (#4643)
* fix(experiments): ensure experiment runs on all valid inputs

* refactor: extract common function
2024-12-09 10:01:18 +00:00
Max Deichmann 1211de3efb chore: release v3.0.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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-12-09 11:15:37 +01:00
Zhiqiang ZHOUandGitHub 9b9fd35f13 fix(openapi): MediaContentType to enum (#4627)
Signed-off-by: STRRL <im@strrl.dev>
2024-12-09 11:13:00 +01:00
Steffen SchmitzandGitHub 407f5dd1ce build: add latest tags to v3 tags (#4648) 2024-12-09 09:59:32 +00:00
Steffen SchmitzandGitHub d7832336dd fix: allow userId filter on /api/public/observations again (#4644)
* fix: allow userId filter on /api/public/observations again

* Update observation-api.servertest.ts
2024-12-09 09:46:51 +00:00
Max DeichmannandGitHub 6362d8bcc9 fix: always pull the project_id for generations queries (#4641)
push
2024-12-09 08:19:15 +00:00
Max Deichmann 35057ea443 chore: release v3.0.0-rc.6
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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
2024-12-09 08:52:46 +01:00
Max DeichmannandGitHub 9adc3bee5b fix: default to empty string for csv export in case of null (#4640) 2024-12-08 22:56:48 +01:00
Max DeichmannandGitHub e57af47d84 fix: fix trace tagging without postgres (#4639)
* fix: fix trace tagging without postgres

* fix: fix trace tagging without postgres
2024-12-08 21:43:00 +00:00
Max DeichmannandGitHub 8f0247d311 fix: fix trace bookmarking (#4638)
fix: fix trace publishing
2024-12-08 22:22:07 +01:00
Max DeichmannandGitHub e96b2a4b0f feat: move exports to ch (#4634) 2024-12-08 22:07:33 +01:00
Max DeichmannandGitHub c389d4f01a feat: move session tests to async suite (#4636)
* feat: move session tests to async suite

* feat: move session tests to async suite

* feat: move session tests to async suite
2024-12-08 13:21:14 +00:00
Max Deichmann 4d8bb0fb95 chore: release v3.0.0-rc.5
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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
2024-12-08 10:52:56 +01:00
Marc KlingenandGitHub 79068ef93f chore: use s3 region auto in docker-compose.yml (#4630) 2024-12-07 17:26:43 +00:00
6c1c231ba7 fix: support reading observations from clickhouse for observations in annotation queue (#4621)
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-12-07 18:10:20 +01:00
Max DeichmannandGitHub b722892c00 fix: check postgres negative flag (#4624)
* fix: check postgres negative flag

* improve dashboard

* improve dashboard

* improve dashboard

* improve dashboard

* perf: fetch observations using timestamps

* perf: fetch observations using timestamps

* perf: fetch observations using timestamps

* perf: fetch observations using timestamps
2024-12-07 14:52:55 +00:00
Max DeichmannandGitHub 4cd1d167da perf: fetch observations using timestamps (#4629) 2024-12-07 13:35:45 +00:00
Steffen SchmitzandGitHub c0f38a8eda chore: hide background migrations on cloud and show queued status (#4628) 2024-12-07 07:59:02 +00:00
marliessophieandGitHub 6be87994a9 fix: only read trace and score from prisma if LANGFUSE_POSTGRES_INGESTION_ENABLED (#4618)
* chore: remove reading trace and score from prisma, only read from clickhouse

* fixup: keep create logic behind flag

* chore: only read from postgres for updating annoation score if `LANGFUSE_POSTGRES_INGESTION_ENABLED`

* chore: only read from postgres for deleting annoation score if `LANGFUSE_POSTGRES_INGESTION_ENABLED`

* chore: throw errors if scores not found in CH

* chore: add audit logs in clickhouse scenario

* only create audit log once on cloud
2024-12-06 18:42:01 +00:00
Max DeichmannandGitHub e90bb60ec8 fix: fix traces publishing in V3 without postgres (#4620)
* make sleep great again

* make sleep great again
2024-12-06 18:22:53 +00:00
Max DeichmannandGitHub 439a65b926 fix: fix numeric chart for dashboards (#4615) 2024-12-06 18:15:25 +00:00
Max DeichmannandGitHub 11af1f4167 feat: increase trace upsert delay (#4619)
* fix: increase trace upsert delay

* fix: increase trace upsert delay
2024-12-06 17:15:23 +00:00
marliessophieandGitHub 2d265eb3f9 feat(comments): add support for comment button on each trace preview in session view (#3848)
* feat(comments): add support for comment button on each trace preview in single session view

* perf: manually timeout comment count query at 500ms without failing overall operation

* push

* chore: query comment reference objects in clickhouse if `env.clickhouse_url`

* remove: deprecated code

* chore: support fetching traces per session from clickhouse

* push

* refactor: adjust tests to populate traces and observations in clickhouse

* refactor: adjust tests and remove redundant code

* push
2024-12-06 16:35:15 +00:00
Max DeichmannandGitHub a93b8ff119 chore: move eval to correct ee directory (#4616)
* chore: move eval to correct ee directory

* chore: move eval to correct ee directory

* chore: move eval to correct ee directory
2024-12-06 16:31:10 +00:00
Max DeichmannandGitHub 81cf48ddf0 feat: set v3 default variables (#4613) 2024-12-06 16:46:09 +01:00
816eec0f75 chore: drop FK v3 constraints (#4612)
* chore: drop FK v3 constraints

* make sleep great again

* make sleep great again

* make sleep great again

* make sleep great again

---------

Co-authored-by: steffen911 <steffen@langfuse.com>
2024-12-06 13:02:54 +00:00
steffen911 fa630d34de chore: release v3.0.0-rc.4
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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
2024-12-06 14:01:36 +01:00
0eb129e186 feat: add ui for background migrations (#4602)
* feat: add ui for background migrations

* chore: add retry for background migrations

* chore: remove redundant import

* chore: invalidate correct state

* chore: unused import

* chore: update the popup text

* chore: add status icon

* chore: move to update menu

---------

Co-authored-by: steffen911 <steffen@langfuse.com>
2024-12-06 12:49:26 +00:00
marliessophieandGitHub 2defaf273a fix(experiments): don't create experiment run items for archived dataset items (#4611)
* fix(experiments): don't create experiment run items for archived dataset items

* fix: type check for input, `typeof null === "object"` in js
2024-12-06 12:02:46 +00:00
Max DeichmannandGitHub b9305e76f5 fix: fix front end confirmations to delete traces (#4601) 2024-12-05 16:55:28 +01:00
Max DeichmannandGitHub 241d1119f9 feat: adjust trace redirect to uniqueness of trace ids to projects (#4598) 2024-12-05 15:24:11 +00:00
Steffen SchmitzandGitHub 4a44adcedd chore: add wait time during validation of clickhouse background migra… (#4596)
chore: add wait time during validation of clickhouse background migrations
2024-12-05 13:53:09 +00:00
Steffen SchmitzandGitHub 9a861dff37 chore: make project deletion asynchronous (#4399)
* chore: make project deletion asynchronous

* chore: delete projects on the worker

* chore: indicate in UI overview that project is being deleted

* chore: remove API keys from Postgres in project deletion call
2024-12-05 13:13:22 +00:00
Steffen SchmitzandGitHub 12ee00f273 chore: check early whether clickhouse url is configured (#4595)
* chore: check early whether clickhouse url is configured

* Update entrypoint.sh
2024-12-05 12:51:23 +00:00
Steffen SchmitzandGitHub f2513fbebf fix: return timestamps in API response as ISO strings (#4593) 2024-12-05 10:49:22 +00:00
marliessophieandGitHub 5be98906e3 feat(url): support AWS S3 signed images in trace/observation preview (#4575)
* feat(url): support AWS S3 signed images in trace/observation preview

* push

* push

* chore: also validate content-type of pre-signed urls
2024-12-05 09:35:20 +00:00
marliessophieandGitHub 4bac94e3fc feat(comments): increase content character limit to 3,000 (#4577)
* feat(comments): increase content character limit to 3,000

* push

* push

* push
2024-12-05 08:38:43 +00:00
Max DeichmannandGitHub 86980da792 fix: show correct scores if dataset runs link same traces (#4582)
* fix: fix scores when dataset runs are linked to the same traces

* fix: fix scores when dataset runs are linked to the same traces
2024-12-04 22:14:18 +00:00
Max DeichmannandGitHub f9be079d62 chore: add aggregateScores tests (#4580)
* chore: add aggregateScores tests

* chore: add aggregateScores tests
2024-12-04 21:06:43 +00:00
Marc KlingenandGitHub d66fac5e29 chore: explicitly mention worker/src/ee (#4581) 2024-12-04 21:02:51 +00:00
Hassieb PakzadandGitHub 83c01bb444 fix(media): add self host env vars (#4578) 2024-12-04 19:07:37 +01:00
Hassieb PakzadandGitHub c937fc7280 chore(cost-tracking): add gpt-4o-2024-11-20 prices (#4576) 2024-12-04 17:10:36 +00:00
Hassieb PakzadandGitHub c8aa4a2d70 chore(ingestion): add back async ingestion processing flag (#4573) 2024-12-04 17:28:18 +01:00
Marc KlingenandGitHub 5d459f0143 fix(cloud): only active evals count towards free plan limit (#4572)
* fix(cloud): only active evals count towards free plan limit

* remove log
2024-12-04 15:22:45 +00:00
Steffen SchmitzandGitHub faa1b71f48 fix: perform batch trace deletion on clickhouse (#4570) 2024-12-04 14:17:09 +00:00
Max DeichmannandGitHub 99e9825e66 perf: improve user metrics for large projects (#4568)
* feat: improve user metrics

* feat: improve user metrics

* feat: improve user metrics

* feat: improve user metrics

* feat: improve user metrics
2024-12-04 14:11:28 +00:00
marliessophieandGitHub 11ad444632 fix(ui): show scrollbar for scores in column visibility (#4569) 2024-12-04 13:56:56 +00:00
Steffen SchmitzGitHubellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
f132aa3969 fix: convert json strings to json in traces api metadata field (#4564)
* fix: convert json strings to json in traces api metadata field

* Update web/src/__tests__/async/traces-api.servertest.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore: stupid ai

* chore: typing

* chore: lint

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2024-12-04 11:11:43 +00:00
Steffen SchmitzandGitHub fa69d4dc75 chore: increase timeout for daily metrics query (#4565) 2024-12-04 10:46:52 +00:00
Max DeichmannandGitHub 6ef6fba4dc chore: double ingestion API rate limits (#4544)
feat
2024-12-04 09:31:44 +00:00
Max DeichmannandGitHub a5078ccbaf chore: remove public users api (#4553) 2024-12-04 09:20:09 +00:00
Marc KlingenandGitHub ba5c1cd70e fix: disable x frame options header on Hugging Face (#4558) 2024-12-04 00:06:19 +01:00
Max DeichmannandGitHub 19ea989b5a perf: add a user_id bloom filter (#4554)
* perf: add a user_id bloom filter

* push

* perf: add a user_id bloom filter
2024-12-03 21:28:52 +00:00
Max DeichmannandGitHub ec0bf37d0b chore: refactor tests async (#4552)
* chore: refactor tests

* chore: refactor tests
2024-12-03 20:03:57 +00:00
Max DeichmannandGitHub 3b4790270f fix: fix time to first token (#4549)
* fix: fix time to first token

* fix: fix time to first token

* fix: fix time to first token

* push

* fix: fix time to first token
2024-12-03 18:05:40 +00:00
Marc Klingen ca3b95312b chore: release v3.0.0-rc.3
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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
2024-12-03 18:53:59 +01:00
b9aaf05acb feat(auth): add KeyCloak authentication option (#2866)
---------

Co-authored-by: RTae <natthanan.bhu@doctorasa.co>
Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-12-03 18:46:09 +01:00
Marc KlingenandGitHub 004aee9f35 feat(ui): add "not found" error page for unknown traces and sessions (#4548)
* feat(ui): add "not found" page for unknown trace ids

* also apply to session
2024-12-03 16:56:03 +00:00
Steffen SchmitzandGitHub 0c9070f78f chore: migrate /metrics/daily API route to Clickhouse (#4546)
* chore: migrate /metrics/daily API route to Clickhouse

* chore: apply final
2024-12-03 16:35:09 +00:00
Marc KlingenandGitHub a91357a958 fix: remove LANGFUSE_CSP_DISABLE (did not work) and disable csp headers on HF Spaces (#4545) 2024-12-03 17:31:18 +01:00
Hassieb PakzadandGitHub 80050cee00 fix(media): add wait time before retries (#4547) 2024-12-03 17:24:43 +01:00
Steffen SchmitzandGitHub b533b1dccd chore: migrate traces all API route to Clickhouse (#4542)
* chore: migrate traces all API route to Clickhouse

* chore: lint

* chore: remove console logs

* chore: naming
2024-12-03 14:04:15 +00:00
Max DeichmannandGitHub 25657d11e4 perf: improve count query performance for scores public api (#4538)
feat
2024-12-03 10:38:41 +00:00
Max DeichmannandGitHub 3dd616a3dc perf: improve single trace public API performance (#4537)
feat
2024-12-03 10:11:04 +00:00
Steffen SchmitzandGitHub 995ec498f4 chore: include projectId in event processing error message (#4535) 2024-12-03 09:22:56 +00:00
Max DeichmannandGitHub 12d7b7f753 perf: improve scores API query performance (#4534)
* feat

* feat

* feat

* push

* feat

* feat

* feat
2024-12-03 09:17:55 +00:00
Marc KlingenandGitHub 2630327a86 chore: add LANGFUSE_CSP_DISABLE to .env.prod.example (#4533) 2024-12-03 01:23:15 +01:00
Marc KlingenandGitHub 4a1fa69af7 feat: optionally disable csp headers via LANGFUSE_CSP_DISABLE=true (#4529) 2024-12-03 01:04:23 +01:00
Max DeichmannandGitHub e53d101b49 fix: show dataset run if not run item exists yet (#4528)
feat
2024-12-02 21:46:54 +00:00
Marc KlingenandGitHub 39539300c1 fix: on cache schema change, delete cached api keys (#4527) 2024-12-02 20:36:12 +00:00
Hassieb PakzadandGitHub 59982db267 fix(io-ui): padding additional input (#4526) 2024-12-02 19:29:37 +01:00
Hassieb PakzadandGitHub fe4299d30d fix(IO-ui): render additional input in pretty view (#4524) 2024-12-02 18:58:25 +01:00
Marc KlingenandGitHub 304b290ab1 fix: no need to decode nextjs path variables, causes issues with special characters (#4522) 2024-12-02 17:09:06 +00:00
Hassieb PakzadandGitHub 97423aa95d fix(media): use db upserts instead of prisma (#4521) 2024-12-02 17:42:53 +01:00
Max DeichmannandGitHub 7aff8e0c26 fix: fix score source public api filter (#4520)
feat
2024-12-02 15:46:59 +00:00
Max DeichmannandGitHub 22761d55c3 chore: add logging for datasets router (#4519)
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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
2024-12-02 16:29:40 +01:00
steffen911 965cf1445d chore: release v3.0.0-rc.2 2024-12-02 16:28:36 +01:00
Marc KlingenandGitHub 922bbb0ce1 docs: add list of dependents to readme (#4518)
* docs: add list of dependents to readme

* push
2024-12-02 15:05:56 +00:00
Max DeichmannandGitHub d821423c6a perf: improve dataset run item performance (#4514) 2024-12-02 15:57:20 +01:00
Steffen SchmitzandGitHub 84b0b7f680 chore: remove backend cloud version checks (#4512)
* chore: remove backend cloud version checks

* chore: lint

* chore: adjust clickhouse healthcheck
2024-12-02 14:41:18 +00:00
Steffen SchmitzandGitHub fba7f4f0c3 build: cancel concurrent PR jobs on new commit (#4517)
* build: cancel concurrent PR jobs on new commit

* chore: adjust pattern

* chore: adjust pattern
2024-12-02 14:31:58 +00:00
Steffen SchmitzandGitHub a9405a2e8b chore: ignore latency for prompts-ui.servertest.ts (#4513) 2024-12-02 14:20:05 +00:00
Marc KlingenandGitHub 2cb3529707 fix(ui): disallow creation of projects if project name already exists in the org (#4516) 2024-12-02 14:12:46 +00:00
Marc KlingenandGitHub 2f13204836 fix(ui): overflow of session view on mobile (#4515) 2024-12-02 14:12:20 +00:00
Steffen SchmitzandGitHub ae2a42bf80 chore: move batch export s3 env variable names into new naming pattern (#4511)
* chore: move batch export s3 env variable names into new naming pattern

* chore: add prefixes correctly

* chore: overwrite batch export feature in CI
2024-12-02 13:49:15 +00:00
Marc KlingenandGitHub 039dee045b feat(cloud): update entitlements to account for limits, add self-hosted pro plan (#3913)
* wip

* push

* add migration

* render correct usage event type in settings

* render number of seats in settings

* wip

* add entitlement limits

* add new actionbutton

* migrate annotation queue create

* add to table date range selectors

* add limit to dashboard date range

* move migration

* fix usage preview in billing settings

* get seats from subscription instead of next invoice

* remove old console.log

* ad pro plan self-hosted

* fix

* readme

* rename / fixes

* push

* only alert on over 90% usage

* push

* push

* push

* push

* add new self host entitlements for v3

* drop batch-export entitlement

* push

* move logic to clickhouse

* push

* fix padding

* fix clickhouse logic
2024-12-02 13:39:02 +00:00
Max DeichmannandGitHub 9831bb8e44 fix: increase attempts for evals (#4487)
feat
2024-12-02 13:37:00 +00:00
Steffen SchmitzandGitHub c6e3a12b08 chore: apply v3 defaults to environment variables (#4498) 2024-12-02 13:50:44 +01:00
Marc KlingenandGitHub 9b01b562a8 fix(ui): account for prereleases in update check (#4503) 2024-11-29 18:34:12 +00:00
Hassieb Pakzad e5a0cec7d6 chore: add sha256 hash to media url span 2024-11-29 18:33:26 +01:00
Steffen SchmitzandGitHub 9375250c5a chore: fix typo in ingestion queue concurrency and adjust default (#4495) 2024-11-29 10:30:11 +00:00
Hassieb PakzadandGitHub 82e5f1a932 feat(playground+evals): add gemini and vertex support (#4485) 2024-11-29 10:07:33 +01:00
Max DeichmannandGitHub 64a62c9f6d perf: improve datasets view (#4486) 2024-11-28 21:21:05 +01:00
44d856747a feat(auth): add GitHub Enterprise Authentication Provider (#4463)
feat: Add GitHub Enterprise Authentication Provider

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-11-28 01:43:43 +00:00
Marc Klingen e17bd364c9 chore: fix ci on fork prs 2024-11-28 02:30:08 +01:00
Marc KlingenandGitHub f38c756244 fix: do not authenticate with Docker Hub for pull request CI (#4477) 2024-11-28 02:20:29 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Marc Klingen
6c62f4b31a chore(deps): bump posthog-node from 3.6.3 to 4.3.1 (#4450)
* chore(deps): bump posthog-node from 3.6.3 to 4.3.1

Bumps [posthog-node](https://github.com/PostHog/posthog-js-lite/tree/HEAD/posthog-node) from 3.6.3 to 4.3.1.
- [Release notes](https://github.com/PostHog/posthog-js-lite/releases)
- [Changelog](https://github.com/PostHog/posthog-js-lite/blob/main/posthog-node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js-lite/commits/posthog-node-v4.3.1/posthog-node)

---
updated-dependencies:
- dependency-name: posthog-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix flush

* shutdown instead of shutdownasync

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-11-28 00:07:51 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fb686d45ee chore(deps): bump stripe from 16.8.0 to 17.4.0 (#4377)
Bumps [stripe](https://github.com/stripe/stripe-node) from 16.8.0 to 17.4.0.
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v16.8.0...v17.4.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-27 23:47:54 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1901c8cb39 chore(deps): bump lucide-react from 0.447.0 to 0.462.0 (#4475)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.447.0 to 0.462.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.462.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-11-27 23:47:05 +00:00
Marc Klingen 66d925ee1f chore: do not run codeql on merge queue 2024-11-28 01:05:07 +01:00
Marc Klingen 1b06385111 chore: run ci on merge groups 2024-11-28 00:23:08 +01:00
Hassieb PakzadandGitHub 6e290b9ad4 fix(media): increase retries (#4474) 2024-11-27 22:57:53 +00:00
Max DeichmannandGitHub 8b8afff705 fix: fix generations search in clickhouse (#4472) 2024-11-27 23:20:21 +01:00
Max DeichmannandGitHub 89fcf27f84 chore: add logs for media API (#4471) 2024-11-27 22:46:44 +01:00
Max DeichmannandGitHub 6717b16679 perf: add improved default performance to traces table (#4469)
* feat

* feat
2024-11-27 21:07:29 +00:00
Cole WinstanleyandGitHub 98e29cb512 fix(auth): add nonce check for Cognito NextAuth provider (#4401) 2024-11-27 17:59:31 +01:00
Marc KlingenandGitHub c7b7a784d7 feat(ui): add more explicit confirmation prompt before deleting a dataset (#4424) 2024-11-27 16:51:44 +00:00
Marc KlingenandGitHub e778d04523 perf: single clickhouse query for cloud usage job across all orgs/projects (#4453) 2024-11-27 17:39:31 +01:00
Hassieb PakzadandGitHub 2a2f50e2d9 feat(media): broaden content type support (#4442)
* feat(media): broaden content type support

* fix file extensions

* add media attachment view in pretty mode
2024-11-27 17:19:54 +01:00
Max DeichmannandGitHub 9d9820dacc chore: remove log line (#4466) 2024-11-27 15:54:39 +01:00
Max DeichmannandGitHub 6e4065eff3 perf: extract traces table metrics from clickhouse traces.all query (#4460) 2024-11-27 14:24:15 +00:00
Marc KlingenandGitHub 993e2a8323 fix(ui): trace timeline peek drawer should not be full-width (#4462) 2024-11-27 14:04:45 +00:00
Steffen SchmitzandGitHub f0c117b35b chore: expand error logging for queue worker errors (#4457) 2024-11-27 12:29:59 +00:00
Max DeichmannandGitHub b7d353a821 perf: increase performance for traces default table (#4459) 2024-11-27 11:36:14 +00:00
Hassieb PakzadandGitHub 1bdc6a4068 fix(media): increase isolationLevel on concurrent upserts (#4458) 2024-11-27 12:23:08 +01:00
Marc KlingenandGitHub 851de49789 perf: prevent duplicated parallel executions of cloud metering job (#4455) 2024-11-27 01:27:07 +00:00
Max DeichmannandGitHub 202a9b715d perf: improve clickhouse traces table perfromance (#4451) 2024-11-26 23:25:45 +00:00
Marc KlingenandGitHub 5fae362b29 refactor(cloud): move usage metering job to clickhouse (#4448) 2024-11-26 21:27:04 +00:00
Max DeichmannandGitHub 38e5c3fac7 Max/lfe 2988 adjust generations table default again (#4446) 2024-11-26 21:01:10 +00:00
Marc KlingenandGitHub 3d6d94fb16 chore(cloud): improve logs of usage metering job (#4447)
chore: improve logs of usage metering job
2024-11-26 20:27:14 +00:00
Marc KlingenandGitHub cb7230ac41 fix: override score name when config is set (#4445) 2024-11-26 20:08:36 +00:00
Steffen SchmitzandGitHub 44ba6eaddf chore: restrict trace.name length to 1000 characters (#4441) 2024-11-26 19:47:54 +01:00
Max DeichmannandGitHub e566dfa5a9 feat: improve generations table default query time (#4440) 2024-11-26 18:12:13 +00:00
Steffen SchmitzandGitHub f9c3f60d49 chore: make trace deletion asynchronous (#4398) 2024-11-26 16:26:31 +00:00
Steffen SchmitzandGitHub aeb56ecde7 chore: explicitly cast names for traces table (#4433) 2024-11-26 17:15:32 +01:00
Marc KlingenandGitHub 9ac84fbd61 fix: do not log stack trace in trpc checkupdate when no internet access (#4436) 2024-11-26 16:33:33 +01:00
Hassieb PakzadandGitHub fe9af426dc fix(playground): unique prompt names in save to prompt (#4437) 2024-11-26 16:22:44 +01:00
Marc KlingenandGitHub 583e90321e fix(auth): improve name and password verification (#4434)
* fix(auth): improve name and password verification

* password complexity
* no url in name

* e2e test
2024-11-26 13:28:43 +00:00
Hassieb Pakzad e52530351a chore: update v3preview docker compose to main tagged images 2024-11-26 13:49:28 +01:00
Hassieb Pakzad 8a52c0b888 chore: update v3preview docker compose to 3 tagged images 2024-11-26 13:32:16 +01:00
Hassieb Pakzad d02c9b4902 chore: release v3.0.0-rc.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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
2024-11-26 12:20:20 +01:00
Hassieb Pakzad 014c3d9dad chore: do not tag v3 tags as latest image 2024-11-26 12:17:51 +01:00
Max DeichmannandGitHub 1d3c74435b fix: change the default of the migration script (#4419) 2024-11-26 09:48:37 +00:00
Steffen SchmitzandGitHub 6b48d7771c build: adjust docker actions to handle v2 branch (#4416)
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-web-async (node20, pg15, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode) (push) Waiting to run
CI/CD / tests-worker (node20, pg12, mode-azure) (push) Waiting to run
CI/CD / tests-worker (node20, pg15, mode-azure) (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
2024-11-26 09:59:57 +01:00
Max DeichmannandGitHub cd0d39b3c2 fix: fix bullmq api (#4425) 2024-11-25 23:18:16 +00:00
Max DeichmannandGitHub 8fcbcdd29d fix: order datasets API correctly (#4423) 2024-11-25 22:09:49 +00:00
Max DeichmannandGitHub e24f65c51d feat: add all queues to get api for bullmq api (#4422) 2024-11-25 21:53:27 +00:00
Marc KlingenandGitHub 38e6464219 chore: improve logging of cloud metering queue (#4420) 2024-11-25 21:22:13 +00:00
Steffen SchmitzandGitHub 86f5c885a1 chore: skip string parsing for metadata string values (#4415) 2024-11-25 18:04:49 +01:00
Hassieb PakzadandGitHub c0ebd06c0c feat(evaluators): add updates (#4360) 2024-11-25 16:51:46 +01:00
Steffen SchmitzandGitHub 0ce5ddcd00 feat: add azure storage provider support (#4386) 2024-11-25 14:57:40 +00:00
Steffen SchmitzandGitHub 7539af3bd9 chore: overwrite numerical type for second filters on observations table (#4412) 2024-11-25 14:39:04 +00:00
Steffen SchmitzandGitHub 60f9147873 fix: use larger numeric type for latency filters (#4385) 2024-11-25 13:53:27 +00:00
Steffen SchmitzandGitHub a594c142e0 chore: use clickhouse in evaluations queues (#4319) 2024-11-25 13:42:17 +00:00
Max DeichmannandGitHub 18905a9872 feat: add all scores to run items UI (#4409) 2024-11-25 14:31:11 +01:00
Hassieb PakzadandGitHub 27f5dd837d chore(costs): add chatgpt-4o-latest prices (#4407) 2024-11-25 13:55:58 +01:00
Max DeichmannandGitHub a921f9db94 feat: exclude env defined operations from clickhouse (#4405) 2024-11-25 12:56:12 +01:00
Marc Klingen b3cd940c73 chore(ui): remove ph badge (#4403) 2024-11-25 10:41:33 +01:00
Max DeichmannandGitHub f91cf1ca65 feat: refactor clickhouse filter builder for SDK APIs (#4402) 2024-11-24 22:45:29 +00:00
Max DeichmannandGitHub c438e89bd0 feat: support scores list endpoint from clickhouse (#4400) 2024-11-24 21:08:44 +00:00
Max DeichmannandGitHub 9b1746b7ed feat: fetch observations via api from clickhouse (#4395) 2024-11-24 09:48:17 +00:00
Steffen SchmitzandGitHub 0611a72c40 fix: allow special characters in openai tokenization (#4396)
* fix: allow special characters in openai tokenization

* chore: update test
2024-11-24 05:26:45 +00:00
Max DeichmannandGitHub 0ba66526e9 fix: do not use memory tables in clickhouse (#4394) 2024-11-23 18:21:30 +01:00
Max DeichmannandGitHub 425202dd1e fix: correctly retrieve scores for datasets (#4387) 2024-11-22 17:20:48 +00:00
Hassieb PakzadandGitHub 882ad83aad chore: upgrade langfuse-langchain (#4384) 2024-11-22 12:12:00 +01:00
Max DeichmannandGitHub 9b12783d08 fix: fix dataset run table to show all scores (#4383) 2024-11-22 10:44:23 +00:00
Marc KlingenandGitHub c6817ae32f chore: add Product Hunt Launch note (#4364) 2024-11-22 08:50:23 +01:00
marliessophieandGitHub 42711a8cc9 fix: don't show error message to user if trace is not yet available for dataset (#4382) 2024-11-22 08:27:54 +01:00
Marc KlingenandGitHub 0344ab2cf9 fix: experimentation should not use structured output (#4379) 2024-11-22 04:57:47 +01:00
Marc KlingenandGitHub df0d12af43 chore: rename default experiment tag langfuse-prompt-experiment (#4378) 2024-11-22 01:01:40 +00:00
marliessophieandGitHub d058ba8861 docs: add link to experimentation docs (#4371) 2024-11-21 22:42:36 +00:00
marliessophieandGitHub d1309905d0 chore: create dataset run items in order of dataset items timestamp (#4370)
* chore: create dataset run items in order of dataset items timestamp
2024-11-21 22:36:47 +01:00
Max DeichmannandGitHub ca6ac3912f fix: fix dataset run item api to always return traceid (#4367) 2024-11-21 20:25:30 +00:00
Max DeichmannandGitHub 9eabc32681 fix: return i/o from single object APIs (#4368) 2024-11-21 20:14:21 +00:00
marliessophieandGitHub 8798e4a499 feat(experiments): trigger experiments via UI (#4333) 2024-11-21 20:00:07 +01:00
Max Deichmann ea8b2da37c chore: release v2.92.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12) (push) Waiting to run
CI/CD / tests-web-async (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-11-21 18:44:30 +01:00
Max DeichmannandGitHub b7f7d10fea fix: fix dataset item api for self-hosters (#4366) 2024-11-21 17:43:48 +00:00
Marc KlingenandGitHub 0279c0f48e docs: add notification for lw 2 - day 4 (#4362) 2024-11-21 17:16:58 +01:00
Max DeichmannandGitHub c29cf3855f feat: SDK API for observations by id (#4295) 2024-11-21 15:28:34 +00:00
Max DeichmannandGitHub c73901369c fix: fix cost filters for traces table (#4325) 2024-11-21 14:21:01 +00:00
Hassieb PakzadandGitHub 0e70f03fc3 fix(media): lookup env var by exact string (#4356) 2024-11-21 10:48:33 +01:00
Max DeichmannandGitHub 74d1f7f717 feat: fetch single trace via API from clickhouse (#4344) 2024-11-21 10:38:24 +01:00
Marc KlingenandGitHub dfcf67184d docs: add lw2-3 info for multi-modal (#4351) 2024-11-20 23:15:19 +01:00
Max DeichmannandGitHub ce641d1e2b fix: do not fail on invalid prompt templates for evals (#4349) 2024-11-20 19:45:12 +00:00
Max DeichmannandGitHub 166b84a81a fix: do not use temp tables for dataset runs (#4347)
fix
2024-11-20 17:56:55 +00:00
Max DeichmannandGitHub f4097a1ae5 fix: dataset item api fix for large dataset runs (#4313) 2024-11-20 16:33:51 +00:00
Max DeichmannandGitHub 8374e889cf security: upgrade cross-spawn (#4339) 2024-11-20 13:32:19 +00:00
Max DeichmannandGitHub 62deaf7e00 sec: upgrade sentry (#4338) 2024-11-20 13:10:54 +00:00
Max DeichmannandGitHub a4cd68b1ad security: upgrade json path (#4335) 2024-11-20 13:32:01 +01:00
marliessophieandGitHub dde28f21fa chore: don't create traces in evaluation service (#4334) 2024-11-20 13:18:56 +01:00
Hassieb PakzadandGitHub a4c040b314 feat(core): add server side tracing of LLM invocations (#4317) 2024-11-20 12:00:31 +01:00
Max DeichmannandGitHub 87995625ca fix: correct traces pagination (#4329) 2024-11-20 02:31:34 +01:00
Marc KlingenandGitHub e717854909 docs: add launch week day 2 note (#4328) 2024-11-20 00:20:47 +01:00
Max DeichmannandGitHub 9e37f2cb17 feat: add more bullmq helpers (#4324) 2024-11-19 21:51:51 +00:00
Max DeichmannandGitHub fc08db08f3 fix: fix bullmq retry (#4323) 2024-11-19 21:24:55 +01:00
Max DeichmannandGitHub 270bc793ba feat: add service endpoint for failed bull events (#4321) 2024-11-19 21:18:40 +01:00
Max DeichmannandGitHub ced2d339ee fix: increase dataset item delay (#4318) 2024-11-19 17:46:49 +01:00
Max Deichmann 43acee5a29 chore: release v2.91.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web-sync (node20, pg12) (push) Waiting to run
CI/CD / tests-web-sync (node20, pg15) (push) Waiting to run
CI/CD / tests-web-async (node20, pg12) (push) Waiting to run
CI/CD / tests-web-async (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-11-19 15:43:51 +01:00
Max DeichmannandGitHub 723098994b feat: move trace not found to 404 (#4270) 2024-11-19 14:40:22 +00:00
Max DeichmannandGitHub 684159abd1 chore: add logging to ingestion API (#4306) 2024-11-19 14:06:40 +00:00
Steffen SchmitzandGitHub dbb482553f chore: move invalid tokenizer errors to warning (#4311) 2024-11-19 13:21:53 +00:00
Hassieb PakzadandGitHub c99a43559d fix(model-prices): upsert if prices are missing (#4312) 2024-11-19 14:08:30 +01:00
Steffen SchmitzandGitHub 3cbe56861f chore: upgrade prisma to 5.22.0 (#4308) 2024-11-19 10:54:13 +00:00
Steffen SchmitzandGitHub 2ef9aad41c chore: only warn on upsert failures if traceSession exists (#4307) 2024-11-19 10:42:40 +00:00
marliessophieandGitHub 087f4a526b feat(datasets): show linked evaluators on dataset (#4277) 2024-11-19 09:40:17 +00:00
Steffen SchmitzandGitHub f876cf6e34 chore: make migrations self-host replica compatible (#4287) 2024-11-19 08:37:08 +00:00
Marc KlingenandGitHub 75fbd56c67 docs: add sidebar notification for dataset comparison view (#4300) 2024-11-19 01:23:39 +00:00
Max DeichmannandGitHub 0c326e56ba feat: fetch sessions by id for public API (#4292) 2024-11-18 22:39:30 +01:00
Max DeichmannandGitHub 2068ba7314 feat: fetch scores by id via API from clickhouse (#4291) 2024-11-18 20:33:05 +00:00
Max DeichmannandGitHub 9841973b16 chore: add web async test setup (#4294) 2024-11-18 21:05:32 +01:00
Max DeichmannandGitHub 1c44000933 perf: do not fetch models for UI (#4288) 2024-11-18 15:56:45 +00:00
Hassieb PakzadandGitHub f71e1aebed feat(media): add ui support (#4285) 2024-11-18 16:22:43 +01:00
marliessophieandGitHub 252fe184b6 feat(evals_on_datasets): remove feature flag (#4289) 2024-11-18 14:35:35 +00:00
marliessophieandGitHub 8034148313 fix(dataset_compare): show latency only if not null (#4282) 2024-11-18 11:41:33 +00:00
marliessophieandGitHub f6746e8f84 feat(datasets): add header to peek view (#4278)
* refactor: extract peek view

* feat: add consistent header view

* link to dataset item detail view
2024-11-18 10:46:05 +01:00
marliessophieandGitHub a48817300b feat(ui): peek view on dataset run compare table (#4131) 2024-11-18 08:28:18 +01:00
Max DeichmannandGitHub a74a820e32 fix: fix dataset runs api (#4275) 2024-11-17 21:49:11 +00:00
Max DeichmannandGitHub e0f9333d2f fix: fix dataset id api (#4274) 2024-11-17 17:54:21 +01:00
Max DeichmannandGitHub 34d4feae27 fix: fix dataset run items api (#4273) 2024-11-17 17:34:33 +01:00
Max DeichmannandGitHub f09ff1f070 feat: serve datasets from clickhouse (#4272) 2024-11-17 17:06:12 +01:00
Max DeichmannandGitHub 9a89c3f002 fix: fix dataset route (#4271) 2024-11-17 16:33:19 +01:00
Max DeichmannandGitHub f1e55b8ac7 feat: move datasets results to logs only (#4269) 2024-11-17 13:39:45 +00:00
Max DeichmannandGitHub d908fb13b3 feat: read dataset fetures from clickhouse (#4264) 2024-11-17 14:18:20 +01:00
Max DeichmannandGitHub 3c02e525bc fix: session id filter on session table (#4267) 2024-11-16 19:09:23 +01:00
Max DeichmannandGitHub 2cf04e4ab0 feat: configure to read from clickhouse only (#4266) 2024-11-16 13:02:01 +00:00
Max DeichmannandGitHub 24a549dc54 feat: move traces.hasany to clickhouse (#4265) 2024-11-16 12:51:25 +00:00
marliessophieandGitHub 63749970c6 chore(ui): enable all feature flags for admins (#4256) 2024-11-15 19:10:19 +01:00
Marc KlingenandGitHub 0d1b4f384a fix(cloud): usage indicator only on hobby plan (#4257) 2024-11-15 18:10:03 +01:00
marliessophieandGitHub 8517670ac2 feat: support evals triggered via dataset run (#4086) 2024-11-15 16:37:55 +01:00
Steffen SchmitzandGitHub 6157287549 chore: upsert tracesessions from clickhouse ingestion pipeline (#4254) 2024-11-15 14:43:39 +00:00
Max DeichmannandGitHub 041345ad3d perf: improve prompt metrics performance (#4251) 2024-11-15 14:28:08 +00:00
Hassieb PakzadandGitHub 7ed865cb4a chore: add media env to v3preview docker compose (#4250) 2024-11-15 14:32:52 +01:00
steffen911 b4d59960c0 chore: release v2.90.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-11-15 14:16:38 +01:00
Steffen SchmitzandGitHub 611b32e965 chore: add tzdata package (#4253) 2024-11-15 14:15:58 +01:00
Steffen SchmitzandGitHub fc94c02058 fix: fail on errors in database migrations (#4252) 2024-11-15 13:49:42 +01:00
Max Deichmann 853ca3c252 chore: release v2.90.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-11-15 11:25:56 +01:00
Steffen SchmitzandGitHub 74e378fe4d chore: fill clickhouse trace for observations with missing traceId (#4246) 2024-11-15 10:14:35 +00:00
Steffen SchmitzandGitHub a4d1288b00 chore: add note for future score create removal (#4248) 2024-11-15 11:03:59 +01:00
Steffen SchmitzandGitHub 91d27dd231 chore: add clickhouse migrations to web entrypoint (#4245) 2024-11-15 09:26:59 +01:00
Max DeichmannandGitHub 6d90635476 feat: exclude project_ids from clickhouse experimentation (#4242) 2024-11-14 21:47:01 +00:00
Max DeichmannandGitHub e9be67efb4 feat: read metrics for prompts table (#4224) 2024-11-14 20:39:47 +00:00
Max DeichmannandGitHub 71d1f2d8e6 feat: add reverse feature flag for clickhouse reads (#4241) 2024-11-14 18:43:38 +00:00
Marc KlingenandGitHub 56233d52eb feat(cloud): move hobby plan usage info to sidebar (#4240) 2024-11-14 18:06:21 +00:00
Max DeichmannandGitHub 5b5c3ba439 chore: improve clickhouse experimentation (#4239) 2024-11-14 18:23:02 +01:00
Steffen SchmitzandGitHub a0c6acf4e4 chore: add query parameter to healthcheck which enables failure on unavailable database (#4238) 2024-11-14 17:08:16 +00:00
Max DeichmannandGitHub bf54c123c1 fix: sessions metrics query (#4237) 2024-11-14 17:43:37 +01:00
Max DeichmannandGitHub 6a30fed1c6 fix: use todate for trace timestamps (#4235) 2024-11-14 15:30:54 +00:00
Steffen SchmitzandGitHub 8c2f8e6247 chore: reduce use of FINAL in clickhouse queries (#4233) 2024-11-14 16:16:34 +01:00
Steffen SchmitzandGitHub 593d1a087d chore: add timestamp filter for dashboard queries (#4234) 2024-11-14 15:10:39 +01:00
Hassieb PakzadandGitHub 633f76f64a feat(multimodal): add media upload / download endpoints (#3989) 2024-11-14 10:26:30 +01:00
Max DeichmannandGitHub ec89fabc5d fix: fix trace filter on score analytics charts (#4225) 2024-11-13 22:45:08 +00:00
Hassieb PakzadandGitHub 26c02ad49c fix(playground): jump to playground for clickhouse reads (#4222) 2024-11-13 20:10:01 +01:00
Max DeichmannandGitHub e1841e016f fix: fix order by in clickhouse (#4221) 2024-11-13 18:38:47 +01:00
Max DeichmannandGitHub 8ebfb3d8a2 fix: fix observations order by (#4220) 2024-11-13 17:25:05 +00:00
Max DeichmannandGitHub 4c0320c565 feat: fetch number of generations for prompts table form clickhouse (#4218) 2024-11-13 17:09:15 +00:00
Max DeichmannandGitHub 5c1f2202cd chore: add experimentation execution time metrics (#4219) 2024-11-13 17:55:03 +01:00
Steffen SchmitzandGitHub e1abbecabd chore: migrate users table to clickhouse (#4191) 2024-11-13 15:40:41 +00:00
Steffen SchmitzandGitHub 52466da955 chore: use event_ts instead of final for obs and score retrieval (#4214) 2024-11-13 15:19:13 +00:00
Max DeichmannandGitHub ede7ad49bf revert: "security: upgrade dd-trace (#4211)" (#4216) 2024-11-13 16:08:07 +01:00
Max DeichmannandGitHub c61acee8e8 security: upgrade dd-trace (#4211) 2024-11-13 13:41:45 +00:00
Steffen SchmitzandGitHub 7ea0a368af chore: handle clickhouse errors gracefully and correct formatting for getTraceById timestamp (#4210) 2024-11-13 13:10:32 +00:00
Steffen SchmitzandGitHub bb07b3508d chore: apply trace timestamp filter correctly on user model query (#4209) 2024-11-13 12:36:35 +00:00
Steffen SchmitzandGitHub 4df24cfde5 chore: remove superfluous isClickhouseAdminEligible checks (#4207) 2024-11-13 10:21:51 +01:00
Hassieb PakzadandGitHub 47c06a77bd fix(playground): disable stream_options for openai adapter (#4206) 2024-11-13 08:45:42 +00:00
Steffen SchmitzandGitHub 7a95b1e1da chore: migrate score analytics dashboard to clickhouse (#4196) 2024-11-13 08:18:05 +00:00
Max DeichmannandGitHub b09f9cbf5a chore: add tests for traces table UI (#4202) 2024-11-12 22:40:50 +00:00
450 changed files with 30842 additions and 10919 deletions
+84
View File
@@ -0,0 +1,84 @@
# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.
# Prisma
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
DIRECT_URL="postgresql://postgres:postgres@localhost:5432/postgres"
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
# Clickhouse
CLICKHOUSE_MIGRATION_URL="clickhouse://localhost:9000"
CLICKHOUSE_URL="http://localhost:8123"
CLICKHOUSE_USER="clickhouse"
CLICKHOUSE_PASSWORD="clickhouse"
CLICKHOUSE_CLUSTER_ENABLED="false"
# Next Auth
# You can generate a new secret on the command line with:
# openssl rand -base64 32
# https://next-auth.js.org/configuration/options#secret
# NEXTAUTH_SECRET=""
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="secret"
# Langfuse Cloud Environment
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION="DEV"
# Langfuse experimental features
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES="true"
# Salt for API key hashing
SALT="salt"
# Email
EMAIL_FROM_ADDRESS="" # Defines the email address to use as the from address.
SMTP_CONNECTION_URL="" # Defines the connection url for smtp server.
# DON'T PANIC: The Azurite Secrets are well-known and meant to be hard-coded
# S3 Batch Exports
LANGFUSE_S3_BATCH_EXPORT_ENABLED=true
LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse
LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=devstoreaccount1
LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
LANGFUSE_S3_BATCH_EXPORT_REGION=auto
LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://localhost:10000/devstoreaccount1
## Necessary for minio compatibility
LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true
LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/
# S3 Media Upload LOCAL
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=devstoreaccount1
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:10000/devstoreaccount1
## Necessary for minio compatibility
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/
# S3 Event Bucket Upload
## Set to true to test uploading all events to S3
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=devstoreaccount1
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
LANGFUSE_S3_EVENT_UPLOAD_REGION=auto
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://localhost:10000/devstoreaccount1
## Necessary for minio compatibility
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
LANGFUSE_USE_AZURE_BLOB=true
# Set during docker build of application
# Used to disable environment verification at build time
# DOCKER_BUILD=1
REDIS_HOST="127.0.0.1"
REDIS_PORT=6379
REDIS_AUTH="myredissecret"
# 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"
+21 -9
View File
@@ -11,6 +11,7 @@ CLICKHOUSE_MIGRATION_URL="clickhouse://localhost:9000"
CLICKHOUSE_URL="http://localhost:8123"
CLICKHOUSE_USER="clickhouse"
CLICKHOUSE_PASSWORD="clickhouse"
CLICKHOUSE_CLUSTER_ENABLED="false"
# Next Auth
# You can generate a new secret on the command line with:
@@ -33,18 +34,29 @@ SALT="salt"
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=http://localhost:9090
S3_ACCESS_KEY_ID=minio
S3_SECRET_ACCESS_KEY=miniosecret
S3_BUCKET_NAME=langfuse
S3_REGION=us-east-1
# S3 Batch Exports
LANGFUSE_S3_BATCH_EXPORT_ENABLED=true
LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse
LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio
LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=miniosecret
LANGFUSE_S3_BATCH_EXPORT_REGION=us-east-1
LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://localhost:9090
## Necessary for minio compatibility
S3_FORCE_PATH_STYLE=true
LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true
LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/
# S3 Media Upload LOCAL
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=miniosecret
LANGFUSE_S3_MEDIA_UPLOAD_REGION=us-east-1
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090
## Necessary for minio compatibility
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/
# S3 Event Bucket Upload
## Set to true to test uploading all events to S3
LANGFUSE_S3_EVENT_UPLOAD_ENABLED=true
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=miniosecret
@@ -66,4 +78,4 @@ REDIS_AUTH="myredissecret"
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# speeds up local development by not executing init scripts on server startup
NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT="false"
NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT="false"
+86
View File
@@ -0,0 +1,86 @@
# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.
# Prisma
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
DIRECT_URL="postgresql://postgres:postgres@localhost:5432/postgres"
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
# Clickhouse
CLICKHOUSE_MIGRATION_URL="clickhouse://localhost:9000"
CLICKHOUSE_URL="http://localhost:8123"
CLICKHOUSE_USER="clickhouse"
CLICKHOUSE_PASSWORD="clickhouse"
CLICKHOUSE_CLUSTER_ENABLED="false"
# Next Auth
# You can generate a new secret on the command line with:
# openssl rand -base64 32
# https://next-auth.js.org/configuration/options#secret
# NEXTAUTH_SECRET=""
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="secret"
# Langfuse Cloud Environment
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION="DEV"
# Langfuse experimental features
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES="true"
# Salt for API key hashing
SALT="salt"
# Email
EMAIL_FROM_ADDRESS="" # Defines the email address to use as the from address.
SMTP_CONNECTION_URL="" # Defines the connection url for smtp server.
# S3 Batch Exports
LANGFUSE_S3_BATCH_EXPORT_ENABLED=true
LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse
LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio
LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=miniosecret
LANGFUSE_S3_BATCH_EXPORT_REGION=us-east-1
LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://localhost:9090
## Necessary for minio compatibility
LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true
LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/
# # S3 Media Upload LOCAL
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=miniosecret
LANGFUSE_S3_MEDIA_UPLOAD_REGION=us-east-1
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090
## Necessary for minio compatibility
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/
# S3 Event Bucket Upload
## Set to true to test uploading all events to S3
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse
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
# DOCKER_BUILD=1
REDIS_HOST="127.0.0.1"
REDIS_PORT=6379
REDIS_AUTH="myredissecret"
# 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"
LANGFUSE_READ_FROM_POSTGRES_ONLY=true
LANGFUSE_RETURN_FROM_CLICKHOUSE=false
LANGFUSE_READ_DASHBOARDS_FROM_CLICKHOUSE=false
LANGFUSE_READ_FROM_CLICKHOUSE_ONLY=false
-22
View File
@@ -1,22 +0,0 @@
# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.
# Prisma
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
DIRECT_URL="postgresql://postgres:postgres@db:5432/postgres"
DATABASE_URL="postgresql://postgres:postgres@db:5432/postgres"
# Next Auth
NEXTAUTH_SECRET="secret"
NEXTAUTH_URL="http://localhost:3000"
# feature flag to enable experimental features locally
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES="false"
SALT="salt"
# Redis
REDIS_HOST="127.0.0.1"
REDIS_PORT=6379
REDIS_AUTH="myredissecret"
# openssl rand -hex 32 used only here
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
+17 -9
View File
@@ -55,6 +55,7 @@ OTEL_SERVICE_NAME="langfuse"
# Auth, optional configuration
# AUTH_DOMAINS_WITH_SSO_ENFORCEMENT=domain1.com,domain2.com
# AUTH_IGNORE_ACCOUNT_FIELDS=foo,bar
# AUTH_DISABLE_USERNAME_PASSWORD=true
# AUTH_DISABLE_SIGNUP=true
# AUTH_SESSION_MAX_AGE=43200 # 30 days in minutes (default)
@@ -67,6 +68,10 @@ OTEL_SERVICE_NAME="langfuse"
# AUTH_GITHUB_CLIENT_ID=
# AUTH_GITHUB_CLIENT_SECRET=
# AUTH_GITHUB_ALLOW_ACCOUNT_LINKING=false
# AUTH_GITHUB_ENTERPRISE_CLIENT_ID=
# AUTH_GITHUB_ENTERPRISE_CLIENT_SECRET=
# AUTH_GITHUB_ENTERPRISE_BASE_URL=
# AUTH_GITHUB_ENTERPRISE_ALLOW_ACCOUNT_LINKING=false
# AUTH_GITLAB_CLIENT_ID=
# AUTH_GITLAB_CLIENT_SECRET=
# AUTH_GITLAB_ALLOW_ACCOUNT_LINKING=false
@@ -87,6 +92,10 @@ OTEL_SERVICE_NAME="langfuse"
# AUTH_COGNITO_CLIENT_SECRET=
# AUTH_COGNITO_ISSUER=
# AUTH_COGNITO_ALLOW_ACCOUNT_LINKING=false
# AUTH_KEYCLOAK_CLIENT_ID=
# AUTH_KEYCLOAK_CLIENT_SECRET=
# AUTH_KEYCLOAK_ISSUER=
# AUTH_KEYCLOAK_ALLOW_ACCOUNT_LINKING=false
# AUTH_CUSTOM_CLIENT_ID=
# AUTH_CUSTOM_CLIENT_SECRET=
# AUTH_CUSTOM_ISSUER=
@@ -100,16 +109,16 @@ OTEL_SERVICE_NAME="langfuse"
# Defines the connection url for smtp server.
# SMTP_CONNECTION_URL=
# S3 storage, optional, used for exports from the UI
# S3_ENDPOINT=
# S3_ACCESS_KEY_ID=
# S3_SECRET_ACCESS_KEY=
# S3_BUCKET_NAME=
# S3_REGION=
# BATCH_EXPORT_DOWNLOAD_LINK_EXPIRATION_HOURS=
# S3 Batch Exports
# LANGFUSE_S3_BATCH_EXPORT_ENABLED=
# LANGFUSE_S3_BATCH_EXPORT_BUCKET=
# LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=
# LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=
# LANGFUSE_S3_BATCH_EXPORT_REGION=
# LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=
# LANGFUSE_S3_BATCH_EXPORT_PREFIX=
# 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/
@@ -242,7 +251,6 @@ OTEL_SERVICE_NAME="langfuse"
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=
# LANGFUSE_INGESTION_CLICKHOUSE_MAX_ATTEMPTS=
# LANGFUSE_LEGACY_INGESTION_WORKER_CONCURRENCY=
# LANGFUSE_ASYNC_INGESTION_PROCESSING="true"
# QUEUE_CONSUMER_LEGACY_INGESTION_QUEUE_IS_ENABLED="true"
## END Langfuse V3 Ingestion
@@ -58,7 +58,6 @@ jobs:
--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
+1
View File
@@ -6,6 +6,7 @@ on:
branches: [main]
pull_request:
branches: [main]
merge_group:
permissions:
contents: read
+124 -15
View File
@@ -10,7 +10,11 @@ on:
merge_group:
pull_request:
branches:
- "*"
- "**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
lint:
@@ -41,6 +45,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
if: github.repository == 'langfuse/langfuse' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
@@ -66,10 +71,10 @@ jobs:
run: |
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
tests-web:
tests-web-sync:
timeout-minutes: 20
runs-on: ubuntu-latest
name: tests-web (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }})
name: tests-web-sync (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }})
strategy:
matrix:
node-version: [20]
@@ -89,6 +94,7 @@ jobs:
with:
version: 9.5.0
- name: Login to Docker Hub
if: github.repository == 'langfuse/langfuse' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
@@ -105,7 +111,8 @@ jobs:
- name: Load default env
run: |
cp .env.dev.example .env
grep -v -e '^S3_BUCKET_NAME=' -e '^REDIS_HOST=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev.example > .env
grep -v -e '^LANGFUSE_S3_BATCH_EXPORT_ENABLED=' -e '^REDIS_HOST=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev.legacy.example > .env
echo "LANGFUSE_POSTGRES_INGESTION_ENABLED=true" >> .env
- name: Run + migrate
run: |
docker compose -f docker-compose.dev.yml up -d
@@ -131,17 +138,90 @@ jobs:
LANGFUSE_INIT_USER_EMAIL: "demo@langfuse.com"
LANGFUSE_INIT_USER_NAME: "Demo User"
LANGFUSE_INIT_USER_PASSWORD: "password"
- name: run test-sync
run: pnpm --filter=web run test-sync
- name: run test-client
run: pnpm --filter=web run test-client
tests-web-async:
timeout-minutes: 20
runs-on: ubuntu-latest
name: tests-web-async (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.blob-provider }})
strategy:
matrix:
node-version: [20]
postgres-version: [12, 15]
blob-provider: ["", "-azure"]
steps:
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 10
- uses: actions/checkout@v4
- name: Install golang-migrate for Clickhouse migrations
run: |
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/bin/migrate
which migrate
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- name: Login to Docker Hub
if: github.repository == 'langfuse/langfuse' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
password: ${{ secrets.DOCKERHUB_TOKEN_READ }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
cache-dependency-path: "pnpm-lock.yaml"
- name: install dependencies
run: |
pnpm install
- name: Load default env
run: |
cp .env.dev${{ matrix.blob-provider }}.example .env
grep -v -e '^LANGFUSE_S3_BATCH_EXPORT_ENABLED=' -e '^REDIS_HOST=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev${{ matrix.blob-provider }}.example > .env
- name: Run + migrate
run: |
docker compose -f docker-compose.dev${{ matrix.blob-provider }}.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
docker compose ps
env:
POSTGRES_VERSION: ${{ matrix.postgres-version }}
- name: Seed DB
run: |
pnpm run db:migrate
pnpm --filter=shared ch:up
- name: Build
run: pnpm run build
- 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
tests-worker:
timeout-minutes: 20
runs-on: ubuntu-latest
name: tests-worker (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }})
name: tests-worker (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.blob-provider }})
strategy:
matrix:
node-version: [20]
postgres-version: [12, 15]
blob-provider: ["", "-azure"]
steps:
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
@@ -152,6 +232,7 @@ jobs:
with:
version: 9.5.0
- name: Login to Docker Hub
if: github.repository == 'langfuse/langfuse' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
@@ -172,12 +253,12 @@ jobs:
which migrate
- name: Load default env
run: |
cp .env.dev.example .env
cp .env.dev.example web/.env
cp .env.dev.example worker/.env
cp .env.dev${{ matrix.blob-provider }}.example .env
cp .env.dev${{ matrix.blob-provider }}.example web/.env
cp .env.dev${{ matrix.blob-provider }}.example worker/.env
- name: Run + migrate
run: |
docker compose -f docker-compose.dev.yml up -d
docker compose -f docker-compose.dev${{ matrix.blob-provider }}.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
docker compose ps
- name: Ensure no unhealthy status
@@ -211,6 +292,7 @@ jobs:
cache: "pnpm"
cache-dependency-path: "pnpm-lock.yaml"
- name: Login to Docker Hub
if: github.repository == 'langfuse/langfuse' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
@@ -243,6 +325,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Login to Docker Hub
if: github.repository == 'langfuse/langfuse' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
@@ -258,10 +341,14 @@ jobs:
- name: install dependencies
run: |
pnpm install
- name: Install golang-migrate for Clickhouse migrations
run: |
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/bin/migrate
which migrate
- name: Load default env
run: |
cp .env.dev.example .env
echo "LANGFUSE_ASYNC_INGESTION_PROCESSING=true" >> .env
echo "LANGFUSE_CACHE_API_KEY_ENABLED=true" >> .env
echo "LANGFUSE_CACHE_PROMPT_ENABLED=true" >> .env
- name: Run + migrate
@@ -269,14 +356,29 @@ jobs:
docker compose -f docker-compose.dev.yml up -d
docker compose ps
sleep 5 # Wait for PostgreSQL to accept connections
- name: Ensure Docker dependencies are healthy
run: |
if docker-compose ps | grep "(unhealthy)"; then
echo "One or more services are unhealthy"
exit 1
else
echo "All services are healthy"
fi
- name: Seed DB
run: |
pnpm run db:migrate
pnpm --filter=shared run ch:up
pnpm run db:seed:examples
- name: Build
run: pnpm run build
- name: Run server
run: (pnpm run start&)
- 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'
- name: Run e2e tests
run: pnpm --filter=web run test:e2e:server
@@ -286,11 +388,12 @@ jobs:
needs:
[
lint,
tests-web,
tests-web-sync,
tests-worker,
e2e-tests,
test-docker-build,
e2e-server-tests,
tests-web-async,
]
if: always()
steps:
@@ -349,13 +452,16 @@ jobs:
images: |
ghcr.io/langfuse/langfuse # GitHub
langfuse/langfuse # Docker Hub
flavor: |
latest=false
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref, '-rc') }}
type=semver,pattern={{major}},enable=${{ !contains(github.ref, '-rc') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v3') && !contains(github.ref, '-rc') }}
- name: Build and push Docker image (web)
uses: docker/build-push-action@v4
with:
@@ -374,13 +480,16 @@ jobs:
images: |
ghcr.io/langfuse/langfuse-worker # GitHub
langfuse/langfuse-worker # Docker Hub
flavor: |
latest=false
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref, '-rc') }}
type=semver,pattern={{major}},enable=${{ !contains(github.ref, '-rc') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v3') && !contains(github.ref, '-rc') }}
- name: Build and push Docker image (worker)
uses: docker/build-push-action@v4
with:
+1 -1
View File
@@ -3,7 +3,7 @@ on:
push:
# Pattern matched against refs/tags
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # Semantic version tags
- "v3.[0-9]+.[0-9]+" # Semantic version tags
jobs:
release:
+8 -3
View File
@@ -6,6 +6,11 @@ on:
push:
branches:
- main
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
snyk:
@@ -15,13 +20,13 @@ jobs:
- name: Build a Docker image
run: docker compose -f docker-compose.build.yml up -d
- name: Run Snyk to check Docker image for vulnerabilities (langfuse-server)
- name: Run Snyk to check Docker image for vulnerabilities (langfuse-web)
continue-on-error: true
uses: snyk/actions/docker@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: langfuse-server
image: langfuse-langfuse-web
args: --file=web/Dockerfile
- name: Upload result to GitHub Code Scanning
@@ -36,7 +41,7 @@ jobs:
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: langfuse-worker
image: langfuse-langfuse-worker
args: --file=worker/Dockerfile
- name: Upload result to GitHub Code Scanning
+2 -1
View File
@@ -37,8 +37,9 @@ yarn-error.log*
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env*
!.env.dev.example
!.env.local.example
!.env.dev-azure.example
!.env.prod.example
!.env.dev.legacy.example
# vercel
.vercel
+2 -2
View File
@@ -2,8 +2,8 @@ Copyright (c) 2023--2024 Langfuse GmbH
Portions of this software are licensed as follows:
- All content that resides under the "ee/" and/or "web/src/ee" directories of this repository, if these directories exist, is licensed under the license defined in "ee/LICENSE".
- All third party components incorporated into the Finto Technologies Software are licensed under the original license provided by the owner of the applicable component.
- All content that resides under the "ee/", "web/src/ee/", and/or "worker/src/ee/" directories of this repository, if these directories exist, is licensed under the license defined in "ee/LICENSE".
- All third party components incorporated into the Langfuse Software are licensed under the original license provided by the owner of the applicable component.
- Content outside of the above mentioned directories or restrictions above is available under the "MIT Expat" license as defined below.
Permission is hereby granted, free of charge, to any person obtaining a copy
+52 -3
View File
@@ -42,9 +42,7 @@
## Langfuse Overview
_Unmute video for voice-over_
https://github.com/langfuse/langfuse/assets/2834609/a94062e9-c782-4ee9-af59-dee6370149a8
[![Langfuse Overview Video](https://github.com/user-attachments/assets/3926b288-ff61-4b95-8aa1-45d041c70866)](https://langfuse.com/watch-demo)
### Develop
@@ -191,3 +189,54 @@ You can opt-out by setting `TELEMETRY_ENABLED=false`.
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=langfuse/langfuse&type=Date" />
</picture>
</a>
### Open Source Projects Using Langfuse
Top open-source Python projects that use Langfuse, ranked by stars ([Source](https://github.com/langfuse/langfuse-docs/blob/main/components-mdx/dependents)):
| Repository | Stars |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----: |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/127165244?s=40&v=4" width="20" height="20" alt=""> &nbsp; [langgenius](https://github.com/langgenius) / [dify](https://github.com/langgenius/dify) | 54865 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/158137808?s=40&v=4" width="20" height="20" alt=""> &nbsp; [open-webui](https://github.com/open-webui) / [open-webui](https://github.com/open-webui/open-webui) | 51531 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/131470832?s=40&v=4" width="20" height="20" alt=""> &nbsp; [lobehub](https://github.com/lobehub) / [lobe-chat](https://github.com/lobehub/lobe-chat) | 49003 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/85702467?s=40&v=4" width="20" height="20" alt=""> &nbsp; [langflow-ai](https://github.com/langflow-ai) / [langflow](https://github.com/langflow-ai/langflow) | 39093 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/130722866?s=40&v=4" width="20" height="20" alt=""> &nbsp; [run-llama](https://github.com/run-llama) / [llama_index](https://github.com/run-llama/llama_index) | 37368 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/139558948?s=40&v=4" width="20" height="20" alt=""> &nbsp; [chatchat-space](https://github.com/chatchat-space) / [Langchain-Chatchat](https://github.com/chatchat-space/Langchain-Chatchat) | 32486 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/128289781?s=40&v=4" width="20" height="20" alt=""> &nbsp; [FlowiseAI](https://github.com/FlowiseAI) / [Flowise](https://github.com/FlowiseAI/Flowise) | 32448 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/31035808?s=40&v=4" width="20" height="20" alt=""> &nbsp; [mindsdb](https://github.com/mindsdb) / [mindsdb](https://github.com/mindsdb/mindsdb) | 26931 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/119600397?s=40&v=4" width="20" height="20" alt=""> &nbsp; [twentyhq](https://github.com/twentyhq) / [twenty](https://github.com/twentyhq/twenty) | 24195 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/60330232?s=40&v=4" width="20" height="20" alt=""> &nbsp; [PostHog](https://github.com/PostHog) / [posthog](https://github.com/PostHog/posthog) | 22618 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/121462774?s=40&v=4" width="20" height="20" alt=""> &nbsp; [BerriAI](https://github.com/BerriAI) / [litellm](https://github.com/BerriAI/litellm) | 15151 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/179202840?s=40&v=4" width="20" height="20" alt=""> &nbsp; [mediar-ai](https://github.com/mediar-ai) / [screenpipe](https://github.com/mediar-ai/screenpipe) | 11037 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/105877416?s=40&v=4" width="20" height="20" alt=""> &nbsp; [formbricks](https://github.com/formbricks) / [formbricks](https://github.com/formbricks/formbricks) | 9386 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/76263028?s=40&v=4" width="20" height="20" alt=""> &nbsp; [anthropics](https://github.com/anthropics) / [courses](https://github.com/anthropics/courses) | 8385 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/78410652?s=40&v=4" width="20" height="20" alt=""> &nbsp; [GreyDGL](https://github.com/GreyDGL) / [PentestGPT](https://github.com/GreyDGL/PentestGPT) | 7374 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/152537519?s=40&v=4" width="20" height="20" alt=""> &nbsp; [superagent-ai](https://github.com/superagent-ai) / [superagent](https://github.com/superagent-ai/superagent) | 5391 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/137907881?s=40&v=4" width="20" height="20" alt=""> &nbsp; [promptfoo](https://github.com/promptfoo) / [promptfoo](https://github.com/promptfoo/promptfoo) | 4976 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/157326433?s=40&v=4" width="20" height="20" alt=""> &nbsp; [onlook-dev](https://github.com/onlook-dev) / [onlook](https://github.com/onlook-dev/onlook) | 4141 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/7250217?s=40&v=4" width="20" height="20" alt=""> &nbsp; [Canner](https://github.com/Canner) / [WrenAI](https://github.com/Canner/WrenAI) | 2526 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/11855343?s=40&v=4" width="20" height="20" alt=""> &nbsp; [pingcap](https://github.com/pingcap) / [autoflow](https://github.com/pingcap/autoflow) | 2061 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/85268109?s=40&v=4" width="20" height="20" alt=""> &nbsp; [MLSysOps](https://github.com/MLSysOps) / [MLE-agent](https://github.com/MLSysOps/MLE-agent) | 1161 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/158137808?s=40&v=4" width="20" height="20" alt=""> &nbsp; [open-webui](https://github.com/open-webui) / [pipelines](https://github.com/open-webui/pipelines) | 1100 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/18422723?s=40&v=4" width="20" height="20" alt=""> &nbsp; [alishobeiri](https://github.com/alishobeiri) / [thread](https://github.com/alishobeiri/thread) | 1074 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/125468716?s=40&v=4" width="20" height="20" alt=""> &nbsp; [topoteretes](https://github.com/topoteretes) / [cognee](https://github.com/topoteretes/cognee) | 971 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/188657705?s=40&v=4" width="20" height="20" alt=""> &nbsp; [bRAGAI](https://github.com/bRAGAI) / [bRAG-langchain](https://github.com/bRAGAI/bRAG-langchain) | 823 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/169500408?s=40&v=4" width="20" height="20" alt=""> &nbsp; [opslane](https://github.com/opslane) / [opslane](https://github.com/opslane/opslane) | 677 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/151867818?s=40&v=4" width="20" height="20" alt=""> &nbsp; [dynamiq-ai](https://github.com/dynamiq-ai) / [dynamiq](https://github.com/dynamiq-ai/dynamiq) | 639 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/48585267?s=40&v=4" width="20" height="20" alt=""> &nbsp; [theopenconversationkit](https://github.com/theopenconversationkit) / [tock](https://github.com/theopenconversationkit/tock) | 514 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/20493493?s=40&v=4" width="20" height="20" alt=""> &nbsp; [andysingal](https://github.com/andysingal) / [llm-course](https://github.com/andysingal/llm-course) | 394 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/132396805?s=40&v=4" width="20" height="20" alt=""> &nbsp; [phospho-app](https://github.com/phospho-app) / [phospho](https://github.com/phospho-app/phospho) | 384 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/178644984?s=40&v=4" width="20" height="20" alt=""> &nbsp; [sentient-engineering](https://github.com/sentient-engineering) / [agent-q](https://github.com/sentient-engineering/agent-q) | 370 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/168552753?s=40&v=4" width="20" height="20" alt=""> &nbsp; [sql-agi](https://github.com/sql-agi) / [DB-GPT](https://github.com/sql-agi/DB-GPT) | 324 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/60330232?s=40&v=4" width="20" height="20" alt=""> &nbsp; [PostHog](https://github.com/PostHog) / [posthog-foss](https://github.com/PostHog/posthog-foss) | 305 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/154247157?s=40&v=4" width="20" height="20" alt=""> &nbsp; [vespperhq](https://github.com/vespperhq) / [vespper](https://github.com/vespperhq/vespper) | 304 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/185116535?s=40&v=4" width="20" height="20" alt=""> &nbsp; [block](https://github.com/block) / [goose](https://github.com/block/goose) | 295 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/609489?s=40&v=4" width="20" height="20" alt=""> &nbsp; [aorwall](https://github.com/aorwall) / [moatless-tools](https://github.com/aorwall/moatless-tools) | 291 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/2357342?s=40&v=4" width="20" height="20" alt=""> &nbsp; [dmayboroda](https://github.com/dmayboroda) / [minima](https://github.com/dmayboroda/minima) | 221 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/66303003?s=40&v=4" width="20" height="20" alt=""> &nbsp; [RobotecAI](https://github.com/RobotecAI) / [rai](https://github.com/RobotecAI/rai) | 172 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/148684274?s=40&v=4" width="20" height="20" alt=""> &nbsp; [i-am-alice](https://github.com/i-am-alice) / [3rd-devs](https://github.com/i-am-alice/3rd-devs) | 148 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/171735272?s=40&v=4" width="20" height="20" alt=""> &nbsp; [8090-inc](https://github.com/8090-inc) / [xrx-sample-apps](https://github.com/8090-inc/xrx-sample-apps) | 138 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/104478511?s=40&v=4" width="20" height="20" alt=""> &nbsp; [babelcloud](https://github.com/babelcloud) / [LLM-RGB](https://github.com/babelcloud/LLM-RGB) | 135 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/15125613?s=40&v=4" width="20" height="20" alt=""> &nbsp; [souzatharsis](https://github.com/souzatharsis) / [tamingLLMs](https://github.com/souzatharsis/tamingLLMs) | 129 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/169401942?s=40&v=4" width="20" height="20" alt=""> &nbsp; [LibreChat-AI](https://github.com/LibreChat-AI) / [librechat.ai](https://github.com/LibreChat-AI/librechat.ai) | 128 |
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/51827949?s=40&v=4" width="20" height="20" alt=""> &nbsp; [deepset-ai](https://github.com/deepset-ai) / [haystack-core-integrations](https://github.com/deepset-ai/haystack-core-integrations) | 126 |
+113 -44
View File
@@ -1,34 +1,53 @@
version: "3.5"
services:
server:
langfuse-web:
build:
dockerfile: ./web/Dockerfile
context: .
args:
- NEXT_PUBLIC_LANGFUSE_CLOUD_REGION=${NEXT_PUBLIC_LANGFUSE_CLOUD_REGION}
depends_on:
- db
- redis
depends_on: &langfuse-depends-on
postgres:
condition: service_healthy
minio:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- NEXTAUTH_SECRET=mysecret
- SALT=mysalt
- ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 # generate via `openssl rand -hex 32`
- 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:-}
environment: &langfuse-web-env
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
NEXTAUTH_SECRET: mysecret
SALT: mysalt
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # generate via `openssl rand -hex 32`
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:-}
CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL:-clickhouse://clickhouse:9000}
CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123}
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
CLICKHOUSE_CLUSTER_ENABLED: ${CLICKHOUSE_CLUSTER_ENABLED:-false}
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-us-east-1}
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000}
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true}
LANGFUSE_S3_EVENT_UPLOAD_PREFIX: ${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_AUTH: ${REDIS_AUTH:-myredissecret}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/public/health"]
@@ -36,26 +55,17 @@ services:
timeout: 10s
retries: 3
worker:
langfuse-worker:
build:
dockerfile: ./worker/Dockerfile
context: .
args:
- NEXT_PUBLIC_LANGFUSE_CLOUD_REGION=${NEXT_PUBLIC_LANGFUSE_CLOUD_REGION}
depends_on:
- db
- redis
depends_on: *langfuse-depends-on
ports:
- "3030:3030"
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- NEXTAUTH_SECRET=mysecret
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true}
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
- PORT=${PORT:-3030}
- REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PORT=${REDIS_PORT:-6379}
- REDIS_AUTH=${REDIS_AUTH:-myredissecret}
<<: *langfuse-web-env
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3030/api/health"]
@@ -63,26 +73,85 @@ services:
timeout: 10s
retries: 3
clickhouse:
image: clickhouse/clickhouse-server
user: "101:101"
container_name: clickhouse
hostname: clickhouse
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: clickhouse
volumes:
- langfuse_clickhouse_data:/var/lib/clickhouse
- langfuse_clickhouse_logs:/var/log/clickhouse-server
ports:
- "8123:8123"
- "9000:9000"
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
interval: 5s
timeout: 5s
retries: 10
start_period: 1s
minio:
image: minio/minio
container_name: minio
entrypoint: sh
# create the 'langfuse' bucket before starting the service
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: miniosecret
ports:
- "9090:9000"
- "9091:9001"
volumes:
- langfuse_minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 1s
timeout: 5s
retries: 5
start_period: 1s
redis:
image: redis:7.2.4
image: redis:7
restart: always
command: >
--requirepass ${REDIS_AUTH:-myredissecret}
restart: always
ports:
- 6379:6379
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 3s
timeout: 10s
retries: 10
db:
image: postgres
postgres:
image: postgres:${POSTGRES_VERSION:-latest}
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 3s
timeout: 3s
retries: 10
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
volumes:
- database_data:/var/lib/postgresql/data
- langfuse_postgres_data:/var/lib/postgresql/data
volumes:
database_data:
langfuse_postgres_data:
driver: local
langfuse_clickhouse_data:
driver: local
langfuse_clickhouse_logs:
driver: local
langfuse_minio_data:
driver: local
+63
View File
@@ -0,0 +1,63 @@
services:
clickhouse:
image: clickhouse/clickhouse-server
user: "101:101"
container_name: clickhouse
hostname: clickhouse
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: clickhouse
volumes:
- langfuse_clickhouse_data:/var/lib/clickhouse
- langfuse_clickhouse_logs:/var/log/clickhouse-server
ports:
- "8123:8123"
- "9000:9000"
depends_on:
- postgres
azurite:
image: mcr.microsoft.com/azure-storage/azurite
container_name: azurite
command: azurite-blob --blobHost 0.0.0.0
ports:
- "10000:10000"
volumes:
- langfuse_azurite_data:/data
redis:
image: redis:7.2.4
restart: always
command: >
--requirepass ${REDIS_AUTH:-myredissecret}
ports:
- 6379:6379
postgres:
image: postgres:${POSTGRES_VERSION:-latest}
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 3s
timeout: 3s
retries: 10
command: ["postgres", "-c", "log_statement=all"]
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
ports:
- 5432:5432
volumes:
- langfuse_postgres_data:/var/lib/postgresql/data
volumes:
langfuse_postgres_data:
driver: local
langfuse_clickhouse_data:
driver: local
langfuse_clickhouse_logs:
driver: local
langfuse_azurite_data:
driver: local
-131
View File
@@ -1,131 +0,0 @@
services:
langfuse-worker:
image: langfuse/langfuse-worker:latest
depends_on: &langfuse-depends-on
postgres:
condition: service_healthy
minio:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "3030:3030"
environment: &langfuse-worker-env
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
SALT: "mysalt"
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # generate via `openssl rand -hex 32`
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-true}
LANGFUSE_ASYNC_INGESTION_PROCESSING: ${LANGFUSE_ASYNC_INGESTION_PROCESSING:-true}
LANGFUSE_ASYNC_CLICKHOUSE_INGESTION_PROCESSING: ${LANGFUSE_ASYNC_CLICKHOUSE_INGESTION_PROCESSING:-true}
CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123}
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
LANGFUSE_S3_EVENT_UPLOAD_ENABLED: ${LANGFUSE_S3_EVENT_UPLOAD_ENABLED:-true}
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-us-east-1}
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000}
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_AUTH: ${REDIS_AUTH:-myredissecret}
langfuse-web:
image: langfuse/langfuse:latest
depends_on: *langfuse-depends-on
ports:
- "3000:3000"
environment:
<<: *langfuse-worker-env
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_SECRET: mysecret
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:-}
clickhouse:
image: clickhouse/clickhouse-server
user: "101:101"
container_name: clickhouse
hostname: clickhouse
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: clickhouse
volumes:
- langfuse_clickhouse_data:/var/lib/clickhouse
- langfuse_clickhouse_logs:/var/log/clickhouse-server
ports:
- "8123:8123"
- "9000:9000"
depends_on:
- postgres
minio:
image: minio/minio
container_name: minio
entrypoint: sh
# create the 'langfuse' bucket before starting the service
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: miniosecret
ports:
- "9090:9000"
- "9091:9001"
volumes:
- langfuse_minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 1s
timeout: 5s
retries: 5
start_period: 1s
redis:
image: redis:7
restart: always
command: >
--requirepass ${REDIS_AUTH:-myredissecret}
ports:
- 6379:6379
healthcheck:
test: [ 'CMD', 'redis-cli', 'ping' ]
interval: 3s
timeout: 10s
retries: 10
postgres:
image: postgres:${POSTGRES_VERSION:-latest}
restart: always
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
interval: 3s
timeout: 3s
retries: 10
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
volumes:
- langfuse_postgres_data:/var/lib/postgresql/data
volumes:
langfuse_postgres_data:
driver: local
langfuse_clickhouse_data:
driver: local
langfuse_clickhouse_logs:
driver: local
langfuse_minio_data:
driver: local
+133 -27
View File
@@ -1,31 +1,131 @@
services:
langfuse-server:
image: langfuse/langfuse:2
depends_on:
db:
langfuse-worker:
image: langfuse/langfuse-worker:3
restart: always
depends_on: &langfuse-depends-on
postgres:
condition: service_healthy
minio:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
ports:
- "3030:3030"
environment: &langfuse-worker-env
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
SALT: "mysalt"
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # generate via `openssl rand -hex 32`
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-true}
CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL:-clickhouse://clickhouse:9000}
CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123}
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
CLICKHOUSE_CLUSTER_ENABLED: ${CLICKHOUSE_CLUSTER_ENABLED:-false}
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto}
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000}
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true}
LANGFUSE_S3_EVENT_UPLOAD_PREFIX: ${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/}
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: ${LANGFUSE_S3_MEDIA_UPLOAD_BUCKET:-langfuse}
LANGFUSE_S3_MEDIA_UPLOAD_REGION: ${LANGFUSE_S3_MEDIA_UPLOAD_REGION:-auto}
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID:-minio}
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://minio:9000}
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE:-true}
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: ${LANGFUSE_S3_MEDIA_UPLOAD_PREFIX:-media/}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_AUTH: ${REDIS_AUTH:-myredissecret}
langfuse-web:
image: langfuse/langfuse:3
restart: always
depends_on: *langfuse-depends-on
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- NEXTAUTH_SECRET=mysecret
- SALT=mysalt
- ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 # generate via `openssl rand -hex 32`
- 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:-}
<<: *langfuse-worker-env
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_SECRET: mysecret
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:-}
LANGFUSE_SDK_CI_SYNC_PROCESSING_ENABLED: ${LANGFUSE_SDK_CI_SYNC_PROCESSING_ENABLED:-false}
LANGFUSE_READ_FROM_POSTGRES_ONLY: ${LANGFUSE_READ_FROM_POSTGRES_ONLY:-false}
LANGFUSE_READ_FROM_CLICKHOUSE_ONLY: ${LANGFUSE_READ_FROM_CLICKHOUSE_ONLY:-true}
LANGFUSE_RETURN_FROM_CLICKHOUSE: ${LANGFUSE_RETURN_FROM_CLICKHOUSE:-true}
db:
image: postgres
clickhouse:
image: clickhouse/clickhouse-server
restart: always
user: "101:101"
container_name: clickhouse
hostname: clickhouse
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: clickhouse
volumes:
- langfuse_clickhouse_data:/var/lib/clickhouse
- langfuse_clickhouse_logs:/var/log/clickhouse-server
ports:
- "8123:8123"
- "9000:9000"
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
interval: 5s
timeout: 5s
retries: 10
start_period: 1s
minio:
image: minio/minio
restart: always
container_name: minio
entrypoint: sh
# create the 'langfuse' bucket before starting the service
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: miniosecret
ports:
- "9090:9000"
- "9091:9001"
volumes:
- langfuse_minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 1s
timeout: 5s
retries: 5
start_period: 1s
redis:
image: redis:7
restart: always
command: >
--requirepass ${REDIS_AUTH:-myredissecret}
ports:
- 6379:6379
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 3s
timeout: 10s
retries: 10
postgres:
image: postgres:${POSTGRES_VERSION:-latest}
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
@@ -33,14 +133,20 @@ services:
timeout: 3s
retries: 10
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
volumes:
- database_data:/var/lib/postgresql/data
- langfuse_postgres_data:/var/lib/postgresql/data
volumes:
database_data:
langfuse_postgres_data:
driver: local
langfuse_clickhouse_data:
driver: local
langfuse_clickhouse_logs:
driver: local
langfuse_minio_data:
driver: local
+2 -2
View File
@@ -28,7 +28,7 @@
"@opentelemetry/api": ">=1.0.0 <1.10.0",
"axios": "^1.7.7",
"next": "^14.2.15",
"next-auth": "^4.24.7",
"next-auth": "^4.24.11",
"zod": "^3.23.8"
},
"devDependencies": {
@@ -47,7 +47,7 @@
},
"pnpm": {
"overrides": {
"jsonpath-plus": "10.0.0"
"jsonpath-plus": "10.0.7"
}
}
}
+2 -5
View File
@@ -1,12 +1,9 @@
import { z } from "zod";
import { env as sharedEnv, removeEmptyEnvVariables } from "@langfuse/shared";
import { 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(removeEmptyEnvVariables(process.env)),
};
export const env = EnvSchema.parse(removeEmptyEnvVariables(process.env));
+1 -1
View File
@@ -58,7 +58,7 @@ types:
docs: The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown.
content:
type: string
docs: The content of the comment. May include markdown. Currently limited to 500 characters.
docs: The content of the comment. May include markdown. Currently limited to 3000 characters.
authorUserId:
type: optional<string>
docs: The id of the user who created the comment.
+11 -5
View File
@@ -126,7 +126,7 @@ types:
docs: The output data of the observation
usage:
type: optional<Usage>
docs: The usage data of the observation
docs: (Deprecated. Use usageDetails and costDetails instead.) The usage data of the observation
level:
type: ObservationLevel
docs: The level of the observation
@@ -139,6 +139,12 @@ types:
promptId:
type: optional<string>
docs: The prompt ID associated with the observation
usageDetails:
type: optional<map<string, integer>>
docs: The usage details of the observation. Key is the name of the usage metric, value is the number of units consumed. The total key is the sum of all (non-total) usage metrics or the total value ingested.
costDetails:
type: optional<map<string, double>>
docs: The cost details of the observation. Key is the name of the cost metric, value is the cost in USD. The total key is the sum of all (non-total) cost metrics or the total value ingested.
ObservationsView:
extends: Observation
@@ -163,13 +169,13 @@ types:
docs: The total price in USD.
calculatedInputCost:
type: optional<double>
docs: The calculated cost of the input in USD
docs: (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the input in USD
calculatedOutputCost:
type: optional<double>
docs: The calculated cost of the output in USD
docs: (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the output in USD
calculatedTotalCost:
type: optional<double>
docs: The calculated total cost in USD
docs: (Deprecated. Use usageDetails and costDetails instead.) The calculated total cost in USD
latency:
type: optional<double>
docs: The latency in seconds.
@@ -178,7 +184,7 @@ types:
docs: The time to the first token in seconds
Usage:
docs: Standard interface for usage and cost
docs: (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost
properties:
input:
docs: Number of input units (e.g. tokens)
+18
View File
@@ -126,6 +126,8 @@ types:
model: optional<string>
modelParameters: optional<map<string, commons.MapValue>>
usage: optional<IngestionUsage>
usageDetails: optional<UsageDetails>
costDetails: optional<map<string, float>>
promptName: optional<string>
promptVersion: optional<integer>
@@ -137,6 +139,8 @@ types:
modelParameters: optional<map<string, commons.MapValue>>
usage: optional<IngestionUsage>
promptName: optional<string>
usageDetails: optional<UsageDetails>
costDetails: optional<map<string, float>>
promptVersion: optional<integer>
ObservationBody:
@@ -312,3 +316,17 @@ types:
properties:
successes: list<IngestionSuccess>
errors: list<IngestionError>
OpenAIUsageSchema:
properties:
prompt_tokens: integer
completion_tokens: integer
total_tokens: integer
prompt_tokens_details: optional<map<string, integer>>
completion_tokens_details: optional<map<string, integer>>
UsageDetails:
discriminated: false
union:
- map<string, integer>
- OpenAIUsageSchema
+161
View File
@@ -0,0 +1,161 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
imports:
commons: ./commons.yml
service:
auth: true
base-path: /api/public
endpoints:
get:
docs: Get a media record
method: GET
path: /media/{mediaId}
path-parameters:
mediaId:
type: string
docs: The unique langfuse identifier of a media record
response: GetMediaResponse
patch:
docs: Patch a media record
method: PATCH
path: /media/{mediaId}
path-parameters:
mediaId:
type: string
docs: The unique langfuse identifier of a media record
request: PatchMediaBody
getUploadUrl:
docs: Get a presigned upload URL for a media record
method: POST
path: /media
request: GetMediaUploadUrlRequest
response: GetMediaUploadUrlResponse
types:
GetMediaResponse:
properties:
mediaId:
type: string
docs: The unique langfuse identifier of a media record
contentType:
type: string
docs: The MIME type of the media record
contentLength:
type: integer
docs: The size of the media record in bytes
uploadedAt:
type: datetime
docs: The date and time when the media record was uploaded
url:
type: string
docs: The download URL of the media record
urlExpiry:
type: string
docs: The expiry date and time of the media record download URL
PatchMediaBody:
properties:
uploadedAt:
type: datetime
docs: The date and time when the media record was uploaded
uploadHttpStatus:
type: integer
docs: The HTTP status code of the upload
uploadHttpError:
type: optional<string>
docs: The HTTP error message of the upload
uploadTimeMs:
type: optional<integer>
docs: The time in milliseconds it took to upload the media record
GetMediaUploadUrlRequest:
properties:
traceId:
type: string
docs: The trace ID associated with the media record
observationId:
type: optional<string>
docs: The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null.
contentType: MediaContentType
contentLength:
type: integer
docs: The size of the media record in bytes
sha256Hash:
type: string
docs: The SHA-256 hash of the media record
field:
type: string
docs: The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata`
GetMediaUploadUrlResponse:
properties:
uploadUrl:
type: optional<string>
docs: The presigned upload URL. If the asset is already uploaded, this will be null
mediaId:
type: string
docs: The unique langfuse identifier of a media record
MediaContentType:
enum:
- value: image/png
name: IMAGE_PNG
- value: image/jpeg
name: IMAGE_JPEG
- value: image/jpg
name: IMAGE_JPG
- value: image/webp
name: IMAGE_WEBP
- value: image/gif
name: IMAGE_GIF
- value: image/svg+xml
name: IMAGE_SVG_XML
- value: image/tiff
name: IMAGE_TIFF
- value: image/bmp
name: IMAGE_BMP
- value: audio/mpeg
name: AUDIO_MPEG
- value: audio/mp3
name: AUDIO_MP3
- value: audio/wav
name: AUDIO_WAV
- value: audio/ogg
name: AUDIO_OGG
- value: audio/oga
name: AUDIO_OGA
- value: audio/aac
name: AUDIO_AAC
- value: audio/mp4
name: AUDIO_MP4
- value: audio/flac
name: AUDIO_FLAC
- value: video/mp4
name: VIDEO_MP4
- value: video/webm
name: VIDEO_WEBM
- value: text/plain
name: TEXT_PLAIN
- value: text/html
name: TEXT_HTML
- value: text/css
name: TEXT_CSS
- value: text/csv
name: TEXT_CSV
- value: application/pdf
name: APPLICATION_PDF
- value: application/msword
name: APPLICATION_MSWORD
- value: application/vnd.ms-excel
name: APPLICATION_MS_EXCEL
- value: application/zip
name: APPLICATION_ZIP
- value: application/json
name: APPLICATION_JSON
- value: application/xml
name: APPLICATION_XML
- value: application/octet-stream
name: APPLICATION_OCTET_STREAM
docs: The MIME type of the media record
+21 -2
View File
@@ -147,10 +147,29 @@ types:
tags:
type: optional<list<string>>
docs: A list of tags associated with the trace referenced by score
GetScoresResponseData:
GetScoresResponseDataNumeric:
extends: commons.NumericScore
properties:
<<: commons.Score
trace: GetScoresResponseTraceData
GetScoresResponseDataCategorical:
extends: commons.CategoricalScore
properties:
trace: GetScoresResponseTraceData
GetScoresResponseDataBoolean:
extends: commons.BooleanScore
properties:
trace: GetScoresResponseTraceData
GetScoresResponseData:
discriminant: dataType
union:
NUMERIC: GetScoresResponseDataNumeric
CATEGORICAL: GetScoresResponseDataCategorical
BOOLEAN: GetScoresResponseDataBoolean
GetScoresResponse:
properties:
data: list<GetScoresResponseData>
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "langfuse",
"version": "2.89.0",
"version": "3.3.0",
"author": "engineering@langfuse.com",
"license": "MIT",
"private": true,
@@ -81,10 +81,10 @@
}
}
},
"packageManager": "pnpm@9.5.0",
"pnpm": {
"overrides": {
"jsonpath-plus": "10.0.0"
"jsonpath-plus": "10.0.7"
}
},
"packageManager": "pnpm@9.5.0"
}
}
@@ -0,0 +1 @@
DROP TABLE traces ON CLUSTER default;
@@ -0,0 +1,32 @@
CREATE TABLE traces ON CLUSTER default (
`id` String,
`timestamp` DateTime64(3),
`name` String,
`user_id` Nullable(String),
`metadata` Map(LowCardinality(String), String),
`release` Nullable(String),
`version` Nullable(String),
`project_id` String,
`public` Bool,
`bookmarked` Bool,
`tags` Array(String),
`input` Nullable(String) CODEC(ZSTD(3)),
`output` Nullable(String) CODEC(ZSTD(3)),
`session_id` Nullable(String),
`created_at` DateTime64(3) DEFAULT now(),
updated_at DateTime64(3) DEFAULT now(),
`event_ts` DateTime64(3),
`is_deleted` UInt8,
INDEX idx_id id TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_res_metadata_key mapKeys(metadata) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_res_metadata_value mapValues(metadata) TYPE bloom_filter(0.01) GRANULARITY 1
) ENGINE = ReplicatedReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(timestamp)
PRIMARY KEY (
project_id,
toDate(timestamp)
)
ORDER BY (
project_id,
toDate(timestamp),
id
);
@@ -0,0 +1 @@
DROP TABLE observations ON CLUSTER default;
@@ -0,0 +1,47 @@
CREATE TABLE observations ON CLUSTER default (
`id` String,
`trace_id` String,
`project_id` String,
`type` LowCardinality(String),
`parent_observation_id` Nullable(String),
`start_time` DateTime64(3),
`end_time` Nullable(DateTime64(3)),
`name` String,
`metadata` Map(LowCardinality(String), String),
`level` LowCardinality(String),
`status_message` Nullable(String),
`version` Nullable(String),
`input` Nullable(String) CODEC(ZSTD(3)),
`output` Nullable(String) CODEC(ZSTD(3)),
`provided_model_name` Nullable(String),
`internal_model_id` Nullable(String),
`model_parameters` Nullable(String),
`provided_usage_details` Map(LowCardinality(String), UInt64),
`usage_details` Map(LowCardinality(String), UInt64),
`provided_cost_details` Map(LowCardinality(String), Decimal64(12)),
`cost_details` Map(LowCardinality(String), Decimal64(12)),
`total_cost` Nullable(Decimal64(12)),
`completion_start_time` Nullable(DateTime64(3)),
`prompt_id` Nullable(String),
`prompt_name` Nullable(String),
`prompt_version` Nullable(UInt16),
`created_at` DateTime64(3) DEFAULT now(),
`updated_at` DateTime64(3) DEFAULT now(),
event_ts DateTime64(3),
is_deleted UInt8,
INDEX idx_id id TYPE bloom_filter() GRANULARITY 1,
INDEX idx_trace_id trace_id TYPE bloom_filter() GRANULARITY 1,
INDEX idx_project_id project_id TYPE bloom_filter() GRANULARITY 1
) ENGINE = ReplicatedReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(start_time)
PRIMARY KEY (
project_id,
`type`,
toDate(start_time)
)
ORDER BY (
project_id,
`type`,
toDate(start_time),
id
);
@@ -0,0 +1 @@
DROP TABLE scores ON CLUSTER default;
@@ -0,0 +1,33 @@
CREATE TABLE scores ON CLUSTER default (
`id` String,
`timestamp` DateTime64(3),
`project_id` String,
`trace_id` String,
`observation_id` Nullable(String),
`name` String,
`value` Float64,
`source` String,
`comment` Nullable(String) CODEC(ZSTD(1)),
`author_user_id` Nullable(String),
`config_id` Nullable(String),
`data_type` String,
`string_value` Nullable(String),
`queue_id` Nullable(String),
`created_at` DateTime64(3) DEFAULT now(),
`updated_at` DateTime64(3) DEFAULT now(),
event_ts DateTime64(3),
`is_deleted` UInt8,
INDEX idx_id id TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_project_trace_observation (project_id, trace_id, observation_id) TYPE bloom_filter(0.001) GRANULARITY 1
) ENGINE = ReplicatedReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(timestamp)
PRIMARY KEY (
project_id,
toDate(timestamp),
name
)
ORDER BY (
project_id,
toDate(timestamp),
name,
id
)
@@ -0,0 +1 @@
ALTER TABLE observations ON CLUSTER default ADD INDEX IF NOT EXISTS idx_project_id project_id TYPE bloom_filter() GRANULARITY 1;
@@ -0,0 +1 @@
ALTER TABLE observations ON CLUSTER default DROP INDEX IF EXISTS idx_project_id;
@@ -0,0 +1 @@
ALTER TABLE traces ON CLUSTER default DROP INDEX IF EXISTS idx_session_id;
@@ -0,0 +1,2 @@
ALTER TABLE traces ON CLUSTER default ADD INDEX IF NOT EXISTS idx_session_id session_id TYPE bloom_filter() GRANULARITY 1;
ALTER TABLE traces ON CLUSTER default MATERIALIZE INDEX IF EXISTS idx_session_id;
@@ -0,0 +1 @@
ALTER TABLE traces ON CLUSTER default DROP INDEX IF EXISTS idx_user_id;
@@ -0,0 +1,2 @@
ALTER TABLE traces ON CLUSTER default ADD INDEX IF NOT EXISTS idx_user_id user_id TYPE bloom_filter() GRANULARITY 1;
ALTER TABLE traces ON CLUSTER default MATERIALIZE INDEX IF EXISTS idx_user_id;
@@ -22,11 +22,11 @@ CREATE TABLE traces (
INDEX idx_res_metadata_value mapValues(metadata) TYPE bloom_filter(0.01) GRANULARITY 1
) ENGINE = ReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(timestamp)
PRIMARY KEY (
project_id,
toDate(timestamp)
project_id,
toDate(timestamp)
)
ORDER BY (
project_id,
toDate(timestamp),
id
);
project_id,
toDate(timestamp),
id
);
@@ -34,14 +34,14 @@ CREATE TABLE observations (
INDEX idx_project_id project_id TYPE bloom_filter() GRANULARITY 1
) ENGINE = ReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(start_time)
PRIMARY KEY (
project_id,
`type`,
toDate(start_time)
)
project_id,
`type`,
toDate(start_time)
)
ORDER BY (
project_id,
`type`,
toDate(start_time),
id
);
project_id,
`type`,
toDate(start_time),
id
);
@@ -21,13 +21,13 @@ CREATE TABLE scores (
INDEX idx_project_trace_observation (project_id, trace_id, observation_id) TYPE bloom_filter(0.001) GRANULARITY 1
) ENGINE = ReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(timestamp)
PRIMARY KEY (
project_id,
toDate(timestamp),
name
)
project_id,
toDate(timestamp),
name
)
ORDER BY (
project_id,
toDate(timestamp),
name,
id
)
project_id,
toDate(timestamp),
name,
id
)
@@ -0,0 +1 @@
ALTER TABLE traces ON CLUSTER default DROP INDEX IF EXISTS idx_user_id;
@@ -0,0 +1,2 @@
ALTER TABLE traces ADD INDEX IF NOT EXISTS idx_user_id user_id TYPE bloom_filter() GRANULARITY 1;
ALTER TABLE traces MATERIALIZE INDEX IF EXISTS idx_user_id;
+25 -7
View File
@@ -1,7 +1,13 @@
#!/bin/bash
# Load environment variables
source ../../.env
[ -f ../../.env ] && source ../../.env
# Check if CLICKHOUSE_URL is configured
if [ -z "${CLICKHOUSE_URL}" ]; then
echo "Info: CLICKHOUSE_URL not configured, skipping migration."
exit 0
fi
# Check if golang-migrate is installed
if ! command -v migrate &> /dev/null
@@ -13,10 +19,22 @@ then
fi
# Construct the database URL
if [ "$CLICKHOUSE_MIGRATION_SSL" = true ] ; then
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&secure=true&skip_verify=true&x-migrations-table-engine=MergeTree"
if [ "$CLICKHOUSE_CLUSTER_ENABLED" == "false" ] ; then
if [ "$CLICKHOUSE_MIGRATION_SSL" = true ] ; then
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&secure=true&skip_verify=true&x-migrations-table-engine=MergeTree"
else
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&x-migrations-table-engine=MergeTree"
fi
# Execute the up command
migrate -source file://clickhouse/migrations/unclustered -database "$DATABASE_URL" down
else
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&x-migrations-table-engine=MergeTree"
fi
# Execute the down command
migrate -source file://clickhouse/migrations -database "$DATABASE_URL" down
if [ "$CLICKHOUSE_MIGRATION_SSL" = true ] ; then
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&secure=true&skip_verify=true&x-cluster-name=default&x-migrations-table-engine=ReplicatedMergeTree"
else
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&x-cluster-name=default&x-migrations-table-engine=ReplicatedMergeTree"
fi
# Execute the up command
migrate -source file://clickhouse/migrations/clustered -database "$DATABASE_URL" down
fi
+53 -3
View File
@@ -1,21 +1,71 @@
import { clickhouseClient } from "@langfuse/shared/src/server";
import {
clickhouseClient,
ObservationRecordReadType,
} from "@langfuse/shared/src/server";
import { prisma } from "../../src/db";
import { redis } from "@langfuse/shared/src/server";
import { prepareClickhouse } from "../../scripts/prepareClickhouse";
import { createDatasets } from "../../prisma/seed";
import { queryClickhouse } from "../../src/server/repositories/clickhouse";
import { convertObservation } from "../../src/server/repositories/observations_converters";
async function main() {
try {
const projectIds = ["7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"]; // Example project IDs
if (
await prisma.project.findFirst({
where: { id: "239ad00f-562f-411d-af14-831c75ddd875" },
})
) {
projectIds.push("239ad00f-562f-411d-af14-831c75ddd875");
}
await prepareClickhouse(projectIds, {
numberOfDays: 3,
totalObservations: 1000,
totalObservations: 10000,
});
const project1 = await prisma.project.findFirst({
where: { id: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a" },
});
const project2 =
projectIds.length > 1
? await prisma.project.findFirst({
where: { id: "239ad00f-562f-411d-af14-831c75ddd875" },
})
: await prisma.project.findFirst();
const query = `
SELECT *
FROM observations o
WHERE o.project_id IN ({projectIds: Array(String)})
LIMIT 2000;
`;
const res = await queryClickhouse<ObservationRecordReadType>({
query,
params: {
projectIds,
},
});
await createDatasets(
project1!,
project2!,
(await Promise.all(res.map(convertObservation))).map((o) => ({
...o,
metadata: {},
modelParameters: {},
input: {},
output: {},
})),
);
console.log("Clickhouse preparation completed successfully.");
} catch (error) {
console.error("Error during Clickhouse preparation:", error);
} finally {
await clickhouseClient.close();
await clickhouseClient().close();
await prisma.$disconnect();
redis?.disconnect();
console.log("Disconnected from Clickhouse.");
+25 -8
View File
@@ -1,7 +1,13 @@
#!/bin/bash
# Load environment variables
source ../../.env
[ -f ../../.env ] && source ../../.env
# Check if CLICKHOUSE_URL is configured
if [ -z "${CLICKHOUSE_URL}" ]; then
echo "Info: CLICKHOUSE_URL not configured, skipping migration."
exit 0
fi
# Check if golang-migrate is installed
if ! command -v migrate &> /dev/null
@@ -13,11 +19,22 @@ then
fi
# Construct the database URL
if [ "$CLICKHOUSE_MIGRATION_SSL" = true ] ; then
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&secure=true&skip_verify=true&x-migrations-table-engine=MergeTree"
else
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&x-migrations-table-engine=MergeTree"
fi
if [ "$CLICKHOUSE_CLUSTER_ENABLED" == "false" ] ; then
if [ "$CLICKHOUSE_MIGRATION_SSL" = true ] ; then
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&secure=true&skip_verify=true&x-migrations-table-engine=MergeTree"
else
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&x-migrations-table-engine=MergeTree"
fi
# Execute the up command
migrate -source file://clickhouse/migrations -database "$DATABASE_URL" up
# Execute the up command
migrate -source file://clickhouse/migrations/unclustered -database "$DATABASE_URL" up
else
if [ "$CLICKHOUSE_MIGRATION_SSL" = true ] ; then
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&secure=true&skip_verify=true&x-cluster-name=default&x-migrations-table-engine=ReplicatedMergeTree"
else
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=default&x-multi-statement=true&x-cluster-name=default&x-migrations-table-engine=ReplicatedMergeTree"
fi
# Execute the up command
migrate -source file://clickhouse/migrations/clustered -database "$DATABASE_URL" up
fi
+12 -10
View File
@@ -43,7 +43,6 @@
"db:generate": "dotenv -e ../../.env -- npx prisma generate",
"db:seed:examples": "dotenv -e ../../.env -- npx prisma db seed -- --environment examples",
"db:seed:load": "dotenv -e ../../.env -- npx prisma db seed -- --environment load",
"ch:status": "dotenv -e ../../.env -- goose -dir './clickhouse/migrations/' status",
"ch:up": "bash clickhouse/scripts/up.sh",
"ch:down": "bash clickhouse/scripts/down.sh",
"ch:drop": "bash clickhouse/scripts/drop.sh",
@@ -60,13 +59,15 @@
"@aws-sdk/client-s3": "^3.675.0",
"@aws-sdk/lib-storage": "^3.675.0",
"@aws-sdk/s3-request-presigner": "^3.679.0",
"@azure/storage-blob": "^12.26.0",
"@clickhouse/client": "^1.4.0",
"@langchain/anthropic": "^0.3.1",
"@langchain/aws": "^0.1.0",
"@langchain/core": "^0.3.9",
"@langchain/openai": "^0.3.0",
"@langchain/anthropic": "^0.3.8",
"@langchain/aws": "^0.1.2",
"@langchain/core": "^0.3.18",
"@langchain/google-vertexai": "^0.1.3",
"@langchain/openai": "^0.3.14",
"@opentelemetry/api": ">=1.0.0 <1.10.0",
"@prisma/client": "^5.20.0",
"@prisma/client": "^5.22.0",
"@react-email/components": "^0.0.19",
"@react-email/render": "^0.0.15",
"@types/bcryptjs": "^2.4.6",
@@ -78,9 +79,10 @@
"exponential-backoff": "^3.1.1",
"ioredis": "^5.4.1",
"kysely": "^0.27.4",
"langchain": "^0.3.2",
"langchain": "^0.3.6",
"langfuse-langchain": "3.30.3",
"lodash": "^4.17.21",
"next-auth": "^4.24.7",
"next-auth": "^4.24.11",
"nodemailer": "^6.9.15",
"prisma-extension-kysely": "^2.1.0",
"uuid": "^9.0.1",
@@ -104,7 +106,7 @@
"kysely-codegen": "^0.16.8",
"nodemon": "^3.1.7",
"prettier": "^3.3.3",
"prisma": "^5.20.0",
"prisma": "^5.22.0",
"prisma-erd-generator": "^1.11.2",
"prisma-kysely": "^1.8.0",
"ts-node": "^10.9.2",
@@ -119,7 +121,7 @@
},
"pnpm": {
"overrides": {
"jsonpath-plus": "10.0.0"
"jsonpath-plus": "10.0.7"
}
}
}
+39
View File
@@ -278,6 +278,8 @@ export type JobExecution = {
end_time: Timestamp | null;
error: string | null;
job_input_trace_id: string | null;
job_input_observation_id: string | null;
job_input_dataset_item_id: string | null;
job_output_score_id: string | null;
};
export type LlmApiKeys = {
@@ -294,6 +296,20 @@ export type LlmApiKeys = {
config: unknown | null;
project_id: string;
};
export type Media = {
id: string;
sha_256_hash: string;
project_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
uploaded_at: Timestamp | null;
upload_http_status: number | null;
upload_http_error: string | null;
bucket_path: string;
bucket_name: string;
content_type: string;
content_length: string;
};
export type MembershipInvitation = {
id: string;
email: string;
@@ -354,6 +370,16 @@ export type Observation = {
completion_start_time: Timestamp | null;
prompt_id: string | null;
};
export type ObservationMedia = {
id: string;
project_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
media_id: string;
trace_id: string;
observation_id: string;
field: string;
};
export type ObservationView = {
id: string;
trace_id: string | null;
@@ -427,6 +453,7 @@ export type Project = {
org_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
name: string;
};
export type ProjectMembership = {
@@ -515,6 +542,15 @@ export type Trace = {
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
};
export type TraceMedia = {
id: string;
project_id: string;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
media_id: string;
trace_id: string;
field: string;
};
export type TraceSession = {
id: string;
created_at: Generated<Timestamp>;
@@ -579,8 +615,10 @@ export type DB = {
job_configurations: JobConfiguration;
job_executions: JobExecution;
llm_api_keys: LlmApiKeys;
media: Media;
membership_invitations: MembershipInvitation;
models: Model;
observation_media: ObservationMedia;
observations: Observation;
observations_view: ObservationView;
organization_memberships: OrganizationMembership;
@@ -594,6 +632,7 @@ export type DB = {
scores: Score;
Session: Session;
sso_configs: SsoConfig;
trace_media: TraceMedia;
trace_sessions: TraceSession;
traces: Trace;
traces_view: TraceView;
@@ -0,0 +1,71 @@
-- CreateTable
CREATE TABLE "media" (
"id" TEXT NOT NULL,
"sha_256_hash" CHAR(44) NOT NULL,
"project_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"uploaded_at" TIMESTAMP(3),
"upload_http_status" INTEGER,
"upload_http_error" TEXT,
"bucket_path" TEXT NOT NULL,
"bucket_name" TEXT NOT NULL,
"content_type" TEXT NOT NULL,
"content_length" BIGINT NOT NULL,
CONSTRAINT "media_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "trace_media" (
"id" TEXT NOT NULL,
"project_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"media_id" TEXT NOT NULL,
"trace_id" TEXT NOT NULL,
"field" TEXT NOT NULL,
CONSTRAINT "trace_media_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "observation_media" (
"id" TEXT NOT NULL,
"project_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"media_id" TEXT NOT NULL,
"trace_id" TEXT NOT NULL,
"observation_id" TEXT NOT NULL,
"field" TEXT NOT NULL,
CONSTRAINT "observation_media_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "media_project_id_sha_256_hash_key" ON "media"("project_id", "sha_256_hash");
-- CreateIndex
CREATE UNIQUE INDEX "trace_media_project_id_trace_id_media_id_field_key" ON "trace_media"("project_id", "trace_id", "media_id", "field");
-- CreateIndex
CREATE INDEX "observation_media_project_id_observation_id_idx" ON "observation_media"("project_id", "observation_id");
-- CreateIndex
CREATE UNIQUE INDEX "observation_media_project_id_trace_id_observation_id_media__key" ON "observation_media"("project_id", "trace_id", "observation_id", "media_id", "field");
-- AddForeignKey
ALTER TABLE "media" ADD CONSTRAINT "media_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "trace_media" ADD CONSTRAINT "trace_media_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "trace_media" ADD CONSTRAINT "trace_media_media_id_fkey" FOREIGN KEY ("media_id") REFERENCES "media"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "observation_media" ADD CONSTRAINT "observation_media_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "observation_media" ADD CONSTRAINT "observation_media_media_id_fkey" FOREIGN KEY ("media_id") REFERENCES "media"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,6 @@
-- DropForeignKey
ALTER TABLE "job_executions" DROP CONSTRAINT "job_executions_job_input_trace_id_fkey";
-- AlterTable
ALTER TABLE "job_executions" ADD COLUMN "job_input_dataset_item_id" TEXT,
ADD COLUMN "job_input_observation_id" TEXT;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "projects" ADD COLUMN "deleted_at" TIMESTAMP(3);
@@ -0,0 +1,25 @@
INSERT INTO models (
id,
project_id,
model_name,
match_pattern,
start_date,
input_price,
output_price,
total_price,
unit,
tokenizer_id,
tokenizer_config
)
VALUES
('cm3x0p8ev000008kyd96800c8', NULL, 'chatgpt-4o-latest', '(?i)^(chatgpt-4o-latest)$', NULL, 0.000005, 0.000015, NULL, 'TOKENS', 'openai', '{ "tokensPerMessage": 3, "tokensPerName": 1, "tokenizerModel": "gpt-4o" }');
INSERT INTO prices (
id,
model_id,
usage_type,
price
)
VALUES
('cm3x0psrz000108kydpxg9o2k', 'cm3x0p8ev000008kyd96800c8', 'input', 0.000005),
('cm3x0pyt7000208ky8737gdla', 'cm3x0p8ev000008kyd96800c8', 'output', 0.000015);
@@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "job_executions" DROP CONSTRAINT "job_executions_job_output_score_id_fkey";
-- DropForeignKey
ALTER TABLE "traces" DROP CONSTRAINT "traces_session_id_project_id_fkey";
+76 -20
View File
@@ -113,6 +113,7 @@ model Project {
orgId String @map("org_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
name String
projectMembers ProjectMembership[]
organization Organization @relation(fields: [orgId], references: [id], onUpdate: Cascade, onDelete: Cascade)
@@ -136,6 +137,9 @@ model Project {
comment Comment[]
annotationQueue AnnotationQueue[]
annotationQueueItem AnnotationQueueItem[]
TraceMedia TraceMedia[]
Media Media[]
ObservationMedia ObservationMedia[]
@@index([orgId])
@@map("projects")
@@ -300,8 +304,6 @@ model Trace {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
JobExecution JobExecution[]
@@index([projectId, timestamp])
@@index([sessionId])
@@index([name])
@@ -467,25 +469,24 @@ enum ObservationLevel {
}
model Score {
id String @id @default(cuid())
timestamp DateTime @default(now())
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
id String @id @default(cuid())
timestamp DateTime @default(now())
projectId String @map("project_id")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
name String
value Float? // always defined if data type is NUMERIC or BOOLEAN, optional for CATEGORICAL
source ScoreSource
authorUserId String? @map("author_user_id")
authorUserId String? @map("author_user_id")
comment String?
traceId String @map("trace_id")
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")
JobExecution JobExecution[]
scoreConfig ScoreConfig? @relation(fields: [configId], references: [id], onDelete: SetNull)
traceId String @map("trace_id")
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")
scoreConfig ScoreConfig? @relation(fields: [configId], references: [id], onDelete: SetNull)
@@unique([id, projectId]) // used for upserts via prisma
@@index(timestamp)
@@ -895,11 +896,13 @@ model JobExecution {
endTime DateTime? @map("end_time")
error String?
jobInputTraceId String? @map("job_input_trace_id")
trace Trace? @relation(fields: [jobInputTraceId], references: [id], onDelete: SetNull) // job remains when traces are deleted
jobInputTraceId String? @map("job_input_trace_id") // no fk constraint - traces in ClickHouse, deletion handled via project cascade
jobInputObservationId String? @map("job_input_observation_id") // no fk constraint - observations in ClickHouse, deletion handled via project cascade
jobInputDatasetItemId String? @map("job_input_dataset_item_id") // no fk constraint - job execution sensible standalone
jobOutputScoreId String? @map("job_output_score_id")
score Score? @relation(fields: [jobOutputScoreId], references: [id], onDelete: SetNull) // job remains when scores are deleted
@@index([projectId, status])
@@index([projectId, id])
@@ -961,3 +964,56 @@ model BatchExport {
@@index([status])
@@map("batch_exports")
}
model Media {
id String @id @default(cuid())
sha256Hash String @map("sha_256_hash") @db.Char(44)
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")
uploadedAt DateTime? @map("uploaded_at")
uploadHttpStatus Int? @map("upload_http_status")
uploadHttpError String? @map("upload_http_error")
bucketPath String @map("bucket_path")
bucketName String @map("bucket_name")
contentType String @map("content_type")
contentLength BigInt @map("content_length")
TraceMedia TraceMedia[]
ObservationMedia ObservationMedia[]
@@unique([projectId, sha256Hash])
@@map("media")
}
model TraceMedia {
id String @id @default(cuid())
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")
mediaId String @map("media_id")
media Media @relation(fields: [mediaId], references: [id], onDelete: Cascade)
traceId String @map("trace_id")
field String @map("field")
@@unique([projectId, traceId, mediaId, field])
@@map("trace_media")
}
model ObservationMedia {
id String @id @default(cuid())
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")
mediaId String @map("media_id")
media Media @relation(fields: [mediaId], references: [id], onDelete: Cascade)
traceId String @map("trace_id")
observationId String @map("observation_id")
field String @map("field")
@@unique([projectId, traceId, observationId, mediaId, field])
@@index([projectId, observationId])
@@map("observation_media")
}
+123 -78
View File
@@ -387,16 +387,62 @@ async function main() {
update: {},
});
for (let datasetNumber = 0; datasetNumber < 2; datasetNumber++) {
const dataset = await prisma.dataset.create({
data: {
name: `demo-dataset-${datasetNumber}`,
description:
datasetNumber === 0 ? "Dataset test description" : undefined,
projectId: project2.id,
metadata: datasetNumber === 0 ? { key: "value" } : undefined,
},
});
await createDatasets(project1, project2, observations);
}
}
main()
.then(async () => {
await prisma.$disconnect();
redis?.disconnect();
logger.info("Disconnected from postgres and redis");
})
.catch(async (e) => {
logger.error(e);
await prisma.$disconnect();
redis?.disconnect();
logger.info("Disconnected from postgres and redis");
process.exit(1);
});
export async function createDatasets(
project1: {
id: string;
orgId: string;
createdAt: Date;
updatedAt: Date;
name: string;
},
project2: {
id: string;
orgId: string;
createdAt: Date;
updatedAt: Date;
name: string;
},
observations: Prisma.ObservationCreateManyInput[],
) {
for (let datasetNumber = 0; datasetNumber < 2; datasetNumber++) {
for (const projectId of [project1.id, project2.id]) {
const datasetName = `demo-dataset-${datasetNumber}`;
// check if ds already exists
const dataset =
(await prisma.dataset.findFirst({
where: {
projectId,
name: datasetName,
},
})) ??
(await prisma.dataset.create({
data: {
name: datasetName,
description:
datasetNumber === 0 ? "Dataset test description" : undefined,
projectId,
metadata: datasetNumber === 0 ? { key: "value" } : undefined,
},
}));
const datasetItemIds = [];
for (let i = 0; i < 18; i++) {
@@ -404,9 +450,12 @@ async function main() {
Math.random() > 0.3
? observations[Math.floor(Math.random() * observations.length)]
: undefined;
if (!sourceObservation) {
continue;
}
const datasetItem = await prisma.datasetItem.create({
data: {
projectId: project2.id,
projectId,
datasetId: dataset.id,
sourceTraceId: sourceObservation?.traceId,
sourceObservationId:
@@ -431,9 +480,16 @@ async function main() {
}
for (let datasetRunNumber = 0; datasetRunNumber < 5; datasetRunNumber++) {
const datasetRun = await prisma.datasetRuns.create({
data: {
projectId: project2.id,
const datasetRun = await prisma.datasetRuns.upsert({
where: {
datasetId_projectId_name: {
datasetId: dataset.id,
projectId,
name: `demo-dataset-run-${datasetRunNumber}`,
},
},
create: {
projectId,
name: `demo-dataset-run-${datasetRunNumber}`,
description: Math.random() > 0.5 ? "Dataset run description" : "",
datasetId: dataset.id,
@@ -445,20 +501,24 @@ async function main() {
["tag1", "tag2"],
][datasetRunNumber % 5],
},
update: {},
});
for (const datasetItemId of datasetItemIds) {
const relevantObservations = observations.filter(
(o) => o.projectId === project2.id,
(o) => o.projectId === projectId,
);
const observation =
relevantObservations[
Math.floor(Math.random() * relevantObservations.length)
];
if (!observation) {
continue;
}
await prisma.datasetRunItems.create({
data: {
projectId: project2.id,
projectId,
datasetItemId,
traceId: observation.traceId as string,
observationId: Math.random() > 0.5 ? observation.id : undefined,
@@ -471,20 +531,6 @@ async function main() {
}
}
main()
.then(async () => {
await prisma.$disconnect();
redis?.disconnect();
logger.info("Disconnected from postgres and redis");
})
.catch(async (e) => {
logger.error(e);
await prisma.$disconnect();
redis?.disconnect();
logger.info("Disconnected from postgres and redis");
process.exit(1);
});
async function uploadObjects(
traces: Prisma.TraceCreateManyInput[],
observations: Prisma.ObservationCreateManyInput[],
@@ -1012,60 +1058,59 @@ async function generatePromptsForProject(projects: Project[]) {
return promptIds;
}
async function generatePrompts(project: Project) {
const promptIds: string[] = [];
const prompts = [
{
id: `prompt-${v4()}`,
projectId: project.id,
createdBy: "user-1",
prompt: "Prompt 1 content",
name: "Prompt 1",
version: 1,
labels: ["production", "latest"],
},
{
id: `prompt-${v4()}`,
projectId: project.id,
createdBy: "user-1",
prompt: "Prompt 2 content",
name: "Prompt 2",
version: 1,
labels: ["production", "latest"],
},
{
id: `prompt-${v4()}`,
projectId: project.id,
createdBy: "API",
prompt: "Prompt 3 content",
name: "Prompt 3 by API",
version: 1,
labels: ["production", "latest"],
},
{
id: `prompt-${v4()}`,
projectId: project.id,
createdBy: "user-1",
prompt: "Prompt 4 content",
name: "Prompt 4",
version: 1,
labels: ["production", "latest"],
tags: ["tag1", "tag2"],
},
];
export const SEED_PROMPTS = [
{
id: `prompt-123`,
createdBy: "user-1",
prompt: "Prompt 1 content",
name: "Prompt 1",
version: 1,
labels: ["production", "latest"],
},
{
id: `prompt-456`,
createdBy: "user-1",
prompt: "Prompt 2 content",
name: "Prompt 2",
version: 1,
labels: ["production", "latest"],
},
{
id: `prompt-789`,
createdBy: "API",
prompt: "Prompt 3 content",
name: "Prompt 3 by API",
version: 1,
labels: ["production", "latest"],
},
{
id: `prompt-abc`,
createdBy: "user-1",
prompt: "Prompt 4 content",
name: "Prompt 4",
version: 1,
labels: ["production", "latest"],
tags: ["tag1", "tag2"],
},
];
for (const prompt of prompts) {
export const PROMPT_IDS: string[] = [];
async function generatePrompts(project: Project) {
const promptIds = [];
for (const prompt of SEED_PROMPTS) {
await prisma.prompt.upsert({
where: {
projectId_name_version: {
projectId: prompt.projectId,
projectId: prompt.id + project.id,
name: prompt.name,
version: prompt.version,
},
id: prompt.id + project.id,
},
create: {
id: prompt.id,
projectId: prompt.projectId,
id: prompt.id + project.id,
projectId: project.id,
createdBy: prompt.createdBy,
prompt: prompt.prompt,
name: prompt.name,
@@ -1073,9 +1118,7 @@ async function generatePrompts(project: Project) {
labels: prompt.labels,
tags: prompt.tags,
},
update: {
id: prompt.id,
},
update: {},
});
promptIds.push(prompt.id);
}
@@ -1129,6 +1172,7 @@ async function generatePrompts(project: Project) {
name: version.name,
version: version.version,
},
id: version.id,
},
create: {
id: version.id,
@@ -1159,6 +1203,7 @@ async function generatePrompts(project: Project) {
name: promptName,
version: i,
},
id: promptId,
},
create: {
id: promptId,
+1 -5
View File
@@ -105,10 +105,6 @@ async function main() {
numberOfDays = 3;
}
logger.info(
`Preparing Clickhouse for ${numOfProjects} projects and ${numberOfDays} days with max Observations ${totalObservations}.`,
);
try {
const projectIds = [
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
@@ -128,7 +124,7 @@ async function main() {
} catch (error) {
logger.error("Error during Clickhouse preparation:", error);
} finally {
await clickhouseClient.close();
await clickhouseClient().close();
await prisma.$disconnect();
redis?.disconnect();
logger.info("Disconnected from Clickhouse.");
+17 -8
View File
@@ -1,3 +1,4 @@
import { SEED_PROMPTS } from "../prisma/seed";
import { prisma } from "../src/db";
import { clickhouseClient, logger } from "../src/server";
@@ -109,10 +110,16 @@ export const prepareClickhouse = async (
map('input', toDecimal64(randUniform(0, 1000), 12), 'output', toDecimal64(randUniform(0, 1000), 12), 'total', toDecimal64(randUniform(0, 2000), 12)) AS provided_cost_details,
map('input', toDecimal64(randUniform(0, 1000), 12), 'output', toDecimal64(randUniform(0, 1000), 12), 'total', toDecimal64(randUniform(0, 2000), 12)) AS cost_details,
toDecimal64(randUniform(0, 2000), 12) AS total_cost,
start_time AS completion_start_time,
toString(rand()) AS prompt_id,
toString(rand()) AS prompt_name,
1000 AS prompt_version,
addMilliseconds(start_time, if(rand() < 0.6, floor(randUniform(0, 500)), floor(randUniform(0, 600)))) AS completion_start_time,
array(${SEED_PROMPTS.map((p) => `concat('${p.id}',project_id)`).join(
",",
)})[(number % ${SEED_PROMPTS.length})+1] AS prompt_id,
array(${SEED_PROMPTS.map((p) => `'${p.name}'`).join(
",",
)})[(number % ${SEED_PROMPTS.length})+1] AS prompt_name,
array(${SEED_PROMPTS.map((p) => `'${p.version}'`).join(
",",
)})[(number % ${SEED_PROMPTS.length})+1] AS prompt_version,
start_time AS created_at,
start_time AS updated_at,
start_time AS event_ts,
@@ -120,6 +127,8 @@ export const prepareClickhouse = async (
FROM numbers(${observationsPerProject});
`;
console.log(observationsQuery);
const scoresQuery = `
INSERT INTO scores
SELECT toString(floor(randUniform(0, 100))) AS id,
@@ -151,7 +160,7 @@ export const prepareClickhouse = async (
for (const query of queries) {
logger.info(`Executing query: ${query}`);
await clickhouseClient.command({
await clickhouseClient().command({
query,
clickhouse_settings: {
wait_end_of_query: 1,
@@ -165,7 +174,7 @@ export const prepareClickhouse = async (
FROM traces
WHERE session_id IS NOT NULL;
`;
const sessionResult = await clickhouseClient.query({
const sessionResult = await clickhouseClient().query({
query: sessionQuery,
format: "JSONEachRow",
});
@@ -203,7 +212,7 @@ export const prepareClickhouse = async (
ORDER BY count() desc
`;
const result = await clickhouseClient.query({
const result = await clickhouseClient().query({
query,
format: "TabSeparated",
});
@@ -234,7 +243,7 @@ export const prepareClickhouse = async (
ORDER BY event_date desc
`;
const result = await clickhouseClient.query({
const result = await clickhouseClient().query({
query,
format: "TabSeparated",
});
+27 -6
View File
@@ -5,6 +5,7 @@ const EnvSchema = z.object({
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
NEXTAUTH_URL: z.string().url().optional(),
REDIS_HOST: z.string().nullish(),
REDIS_PORT: z.coerce
.number({
@@ -27,9 +28,23 @@ const EnvSchema = z.object({
.optional(),
LANGFUSE_CACHE_PROMPT_ENABLED: z.enum(["true", "false"]).default("false"),
LANGFUSE_CACHE_PROMPT_TTL_SECONDS: z.coerce.number().default(60 * 60),
CLICKHOUSE_URL: z.string().url().optional(),
CLICKHOUSE_USER: z.string().optional(),
CLICKHOUSE_PASSWORD: z.string().optional(),
CLICKHOUSE_URL: z.string().url(),
CLICKHOUSE_USER: z.string(),
CLICKHOUSE_PASSWORD: z.string(),
LANGFUSE_SDK_CI_SYNC_PROCESSING_ENABLED: z
.enum(["true", "false"])
.default("false"),
LANGFUSE_CLICKHOUSE_INGESTION_ENABLED: z
.enum(["true", "false"])
.default("true"),
LANGFUSE_POSTGRES_INGESTION_ENABLED: z
.enum(["true", "false"])
.default("false"),
LANGFUSE_INGESTION_QUEUE_DELAY_MS: z.coerce
.number()
.nonnegative()
.default(15_000),
SALT: z.string().optional(), // used by components imported by web package
LANGFUSE_LOG_LEVEL: z
.enum(["trace", "debug", "info", "warn", "error", "fatal"])
@@ -38,8 +53,9 @@ const EnvSchema = z.object({
ENABLE_AWS_CLOUDWATCH_METRIC_PUBLISHING: z
.enum(["true", "false"])
.default("false"),
LANGFUSE_S3_EVENT_UPLOAD_ENABLED: z.enum(["true", "false"]).default("false"),
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: z.string().optional(),
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: z.string({
required_error: "Langfuse requires a bucket name for S3 Event Uploads.",
}),
LANGFUSE_S3_EVENT_UPLOAD_PREFIX: z.string().default(""),
LANGFUSE_S3_EVENT_UPLOAD_REGION: z.string().optional(),
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: z.string().optional(),
@@ -48,6 +64,11 @@ const EnvSchema = z.object({
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: z
.enum(["true", "false"])
.default("false"),
LANGFUSE_USE_AZURE_BLOB: z.enum(["true", "false"]).default("false"),
STRIPE_SECRET_KEY: z.string().optional(),
});
export const env = EnvSchema.parse(removeEmptyEnvVariables(process.env));
export const env: z.infer<typeof EnvSchema> =
process.env.DOCKER_BUILD === "1"
? (process.env as any)
: EnvSchema.parse(removeEmptyEnvVariables(process.env));
@@ -1,5 +1,7 @@
import { z } from "zod";
const MAX_COMMENT_LENGTH = 3000;
const COMMENT_OBJECT_TYPES = [
"TRACE",
"OBSERVATION",
@@ -9,7 +11,7 @@ const COMMENT_OBJECT_TYPES = [
export const CreateCommentData = z.object({
projectId: z.string(),
content: z.string().trim().min(1).max(500),
content: z.string().trim().min(1).max(MAX_COMMENT_LENGTH),
objectId: z.string(),
objectType: z.enum(COMMENT_OBJECT_TYPES),
});
@@ -3,15 +3,17 @@ export const planLabels = {
"cloud:hobby": "Hobby",
"cloud:pro": "Pro",
"cloud:team": "Team",
"self-hosted:enterprise": "Enterprise",
"self-hosted:pro": "Pro (self-hosted)",
"self-hosted:enterprise": "Enterprise (self-hosted)",
} as const;
export type Plan = keyof typeof planLabels;
export const plans = Object.keys(planLabels) as Plan[];
// This function is kept here to ensure consistency when updating plan names in the future.
// These functions are kept here to ensure consistency when updating plan names in the future.
export const isCloudPlan = (plan: Plan) => plan.startsWith("cloud");
export const isSelfHostedPlan = (plan: Plan) => plan.startsWith("self-hosted");
export const isPlan = (value: string): value is Plan =>
plans.includes(value as Plan);
+26 -2
View File
@@ -5,6 +5,7 @@ export const langfuseObjects = [
"span",
"generation",
"event",
"dataset_item",
] as const;
// variable mapping stored in the db for eval templates
@@ -21,7 +22,7 @@ export const variableMapping = z
(value) => value.langfuseObject === "trace" || value.objectName !== null,
{
message: "objectName is required for langfuseObjects other than trace",
}
},
);
export const variableMappingList = z.array(variableMapping);
@@ -44,7 +45,7 @@ const observationCols = [
{ name: "Output", id: "output", internal: 'o."output"' },
];
export const availableEvalVariables = [
export const availableTraceEvalVariables = [
{
id: "trace",
display: "Trace",
@@ -76,6 +77,28 @@ export const availableEvalVariables = [
},
];
export const availableDatasetEvalVariables = [
{
id: "dataset_item",
display: "Dataset item",
availableColumns: [
{
name: "Metadata",
id: "metadata",
type: "stringObject",
internal: 'd."metadata"',
},
{ name: "Input", id: "input", internal: 'd."input"' },
{
name: "Expected output",
id: "expected_output",
internal: 'd."expected_output"',
},
],
},
...availableTraceEvalVariables,
];
export const OutputSchema = z.object({
reasoning: z.string(),
score: z.string(),
@@ -83,6 +106,7 @@ export const OutputSchema = z.object({
export enum EvalTargetObject {
Trace = "trace",
Dataset = "dataset",
}
export const DEFAULT_TRACE_JOB_DELAY = 10_000;
@@ -0,0 +1,15 @@
import { Prisma } from "../../db";
export const datasetItemMatchesVariable = (
input: Prisma.JsonValue,
variable: string,
) => {
if (
input === null ||
input === undefined ||
typeof input !== "object" ||
Array.isArray(input)
)
return false;
return Object.keys(input).includes(variable);
};
@@ -6,7 +6,7 @@ import { isPresent } from "../../utils/typeChecks";
import {
jsonSchema,
paginationMetaResponseZod,
paginationZod,
publicApiPaginationZod,
} from "../../utils/zod";
/**
@@ -17,7 +17,7 @@ export type ValidatedScoreConfig = z.infer<typeof ValidatedScoreConfigSchema>;
const validateCategories = (
categories: ConfigCategory[],
ctx: z.RefinementCtx
ctx: z.RefinementCtx,
) => {
const uniqueNames = new Set<string>();
const uniqueValues = new Set<number>();
@@ -94,7 +94,7 @@ const BooleanScoreConfig = z.object({
return categories.every(
(category, index) =>
category.label === expectedCategories[index].label &&
category.value === expectedCategories[index].value
category.value === expectedCategories[index].value,
);
}),
});
@@ -123,7 +123,7 @@ const ValidatedScoreConfigSchema = z
minValue: z.undefined().nullish(),
dataType: z.literal("CATEGORICAL"),
categories: Categories.superRefine(validateCategories),
})
}),
),
ScoreConfigBase.merge(BooleanScoreConfig),
])
@@ -150,7 +150,7 @@ const ValidatedScoreConfigSchema = z
*/
export const filterAndValidateDbScoreConfigList = (
scoreConfigs: ScoreConfigDbType[],
onParseError?: (error: z.ZodError) => void
onParseError?: (error: z.ZodError) => void,
): ValidatedScoreConfig[] =>
scoreConfigs.reduce((acc, ts) => {
const result = ValidatedScoreConfigSchema.safeParse(ts);
@@ -170,7 +170,7 @@ export const filterAndValidateDbScoreConfigList = (
* @throws error if score fails validation
*/
export const validateDbScoreConfig = (
scoreConfig: ScoreConfigDbType
scoreConfig: ScoreConfigDbType,
): ValidatedScoreConfig => ValidatedScoreConfigSchema.parse(scoreConfig);
/**
@@ -205,7 +205,7 @@ export const PostScoreConfigBody = z
z.object({
dataType: z.literal("BOOLEAN"),
categories: z.undefined().nullish(),
})
}),
),
])
.superRefine((data, ctx) => {
@@ -227,7 +227,7 @@ export const PostScoreConfigResponse = ValidatedScoreConfigSchema;
// GET /score-configs
export const GetScoreConfigsQuery = z.object({
...paginationZod,
...publicApiPaginationZod,
});
export const GetScoreConfigsResponse = z.object({
@@ -6,7 +6,7 @@ import { isPresent, stringDateTime } from "../../utils/typeChecks";
import {
NonEmptyString,
paginationMetaResponseZod,
paginationZod,
publicApiPaginationZod,
} from "../../utils/zod";
import { Category as ConfigCategory } from "./scoreConfigTypes";
@@ -84,13 +84,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({
@@ -98,7 +98,7 @@ export const ScoreBodyWithoutConfig = z.discriminatedUnion("dataType", [
message: "Value must be either 0 or 1",
}),
dataType: z.literal("BOOLEAN"),
})
}),
),
]);
@@ -162,7 +162,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);
@@ -199,14 +199,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({
@@ -216,14 +216,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(),
})
}),
),
]);
@@ -231,7 +231,7 @@ export const PostScoresResponse = z.object({ id: z.string() });
// GET /scores
export const GetScoresQuery = z.object({
...paginationZod,
...publicApiPaginationZod,
userId: z.string().nullish(),
dataType: z.enum(ScoreDataType).nullish(),
configId: z.string().nullish(),
@@ -260,7 +260,7 @@ const LegacyGetScoreResponseDataV1 = z.intersection(
userId: z.string().nullish(),
tags: z.array(z.string()).nullish(),
}),
})
}),
);
export const GetScoresResponse = z.object({
data: z.array(LegacyGetScoreResponseDataV1),
@@ -269,7 +269,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) => {
@@ -282,7 +282,7 @@ export const legacyFilterAndValidateV1GetScoreList = (
}
return acc;
},
[] as z.infer<typeof LegacyGetScoreResponseDataV1>[]
[] as z.infer<typeof LegacyGetScoreResponseDataV1>[],
);
// GET /scores/{scoreId}
+4 -2
View File
@@ -7,15 +7,14 @@ export * from "./interfaces/customLLMProviderConfigSchemas";
export * from "./tableDefinitions";
export * from "./types";
export * from "./tableDefinitions/tracesTable";
export * from "./server/auth/apiKeys";
export * from "./observationsTable";
export * from "./utils/zod";
export * from "./utils/json";
export * from "./utils/stringChecks";
export * from "./utils/objects";
export * from "./utils/typeChecks";
export * from "./features/entitlements/plans";
export * from "./interfaces/rate-limits";
export { env } from "./env";
// llm api
export * from "./server/llm/types";
@@ -33,6 +32,9 @@ export * from "./features/scores";
// comments
export * from "./features/comments/types";
// experiments
export * from "./features/experiments/utils";
// export db types only
export * from "@prisma/client";
export { type DB } from "../prisma/generated/types";
@@ -10,3 +10,19 @@ export const BedrockCredentialSchema = z
})
.optional();
export type BedrockCredential = z.infer<typeof BedrockCredentialSchema>;
export const GCPServiceAccountKeySchema = z.object({
type: z.literal("service_account"),
project_id: z.string(),
private_key_id: z.string(),
private_key: z.string(),
client_email: z.string(),
client_id: z.string(),
auth_uri: z.string(),
token_uri: z.string(),
auth_provider_x509_cert_url: z.string(),
client_x509_cert_url: z.string(),
});
export type GCPServiceAccountKey = z.infer<typeof GCPServiceAccountKeySchema>;
export default GCPServiceAccountKeySchema;
@@ -15,6 +15,7 @@ export const filterOperators = {
],
numberObject: ["=", ">", "<", ">=", "<="],
boolean: ["=", "<>"],
null: ["is null", "is not null"],
} as const;
export const timeFilter = z.object({
@@ -68,6 +69,12 @@ export const booleanFilter = z.object({
operator: z.enum(filterOperators.boolean),
value: z.boolean(),
});
export const nullFilter = z.object({
type: z.literal("null"),
column: z.string(),
operator: z.enum(filterOperators.null),
value: z.literal(""),
});
export const singleFilter = z.discriminatedUnion("type", [
timeFilter,
stringFilter,
@@ -77,4 +84,5 @@ export const singleFilter = z.discriminatedUnion("type", [
stringObjectFilter,
numberObjectFilter,
booleanFilter,
nullFilter,
]);
@@ -0,0 +1,65 @@
import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers/oauth";
import type { GithubProfile, GithubEmail } from "next-auth/providers/github";
export function GitHubEnterpriseProvider<P extends GithubProfile>(
options: OAuthUserConfig<P> & {
enterprise?: {
baseUrl?: string;
};
}
): OAuthConfig<P> {
const baseUrl = options?.enterprise?.baseUrl ?? "https://github.com"
const apiBaseUrl = options?.enterprise?.baseUrl
? `${options?.enterprise?.baseUrl}/api/v3`
: "https://api.github.com"
return {
id: "github-enterprise",
name: "GitHub Enterprise",
type: "oauth",
authorization: {
url: `${baseUrl}/login/oauth/authorize`,
params: { scope: "read:user user:email" },
},
token: `${baseUrl}/login/oauth/access_token`,
userinfo: {
url: `${apiBaseUrl}/user`,
async request({ client, tokens }) {
const profile = await client.userinfo(tokens.access_token!)
if (!profile.email) {
// If the user does not have a public email, get another via the GitHub API
// See https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user
const res = await fetch(`${apiBaseUrl}/user/emails`, {
headers: { Authorization: `token ${tokens.access_token}` },
})
if (res.ok) {
const emails: GithubEmail[] = await res.json()
profile.email = (emails.find((e) => e.primary) ?? emails[0]).email
}
}
return profile
},
},
profile(profile) {
return {
id: profile.id.toString(),
name: profile.name ?? profile.login,
email: profile.email,
image: profile.avatar_url,
}
},
style: {
logo: "https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/next-auth/provider-logos/github.svg",
logoDark:
"https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/next-auth/provider-logos/github-dark.svg",
bg: "#fff",
bgDark: "#000",
text: "#000",
textDark: "#fff",
},
options,
}
}
+16 -12
View File
@@ -1,24 +1,28 @@
import { createClient } from "@clickhouse/client";
import { env } from "../../env";
import { NodeClickHouseClientConfigOptions } from "@clickhouse/client/dist/config";
export type ClickhouseClientType = ReturnType<typeof createClient>;
export const clickhouseClient = createClient({
url: env.CLICKHOUSE_URL,
username: env.CLICKHOUSE_USER,
password: env.CLICKHOUSE_PASSWORD,
database: "default",
clickhouse_settings: {
async_insert: 1,
wait_for_async_insert: 1, // if disabled, we won't get errors from clickhouse
},
});
export const clickhouseClient = (opts?: NodeClickHouseClientConfigOptions) =>
createClient({
...opts,
url: env.CLICKHOUSE_URL,
username: env.CLICKHOUSE_USER,
password: env.CLICKHOUSE_PASSWORD,
database: "default",
clickhouse_settings: {
async_insert: 1,
wait_for_async_insert: 1, // if disabled, we won't get errors from clickhouse
},
});
export const defaultClickhouseClient = clickhouseClient();
/**
* Accepts a JavaScript date and returns the DateTime in format YYYY-MM-DD HH:MM:SS
*/
export const convertDateToClickhouseDateTime = (date: Date): string => {
// 2024-11-06T20:37:00.000Z -> 2024-11-06 21:37:00
return date.toISOString().slice(0, 19).replace("T", " ");
// 2024-11-06T20:37:00.123Z -> 2024-11-06 21:37:00.123
return date.toISOString().replace("T", " ").replace("Z", "");
};
+3 -1
View File
@@ -106,10 +106,12 @@ export function tableColumnsToSqlFilter(
", ",
)}] `;
break;
case "boolean":
valuePrisma = Prisma.sql`${filter.value}`;
break;
case "null":
valuePrisma = Prisma.sql``;
break;
}
const jsonKeyPrisma =
filter.type === "stringObject" || filter.type === "numberObject"
+16 -1
View File
@@ -1,10 +1,12 @@
export * from "./services/S3StorageService";
export * from "./services/StorageService";
export * from "./services/email/organizationInvitation/sendMembershipInvitationEmail";
export * from "./services/email/batchExportSuccess/sendBatchExportSuccessEmail";
export * from "./services/email/passwordReset/sendResetPasswordVerificationRequest";
export * from "./services/PromptService";
export * from "./services/traces-ui-table-service";
export * from "./auth/apiKeys";
export * from "./auth/customSsoProvider";
export * from "./auth/gitHubEnterpriseProvider";
export * from "./llm/fetchLLMCompletion";
export * from "./llm/types";
export * from "./utils/DatabaseReadStream";
@@ -15,13 +17,22 @@ export * from "./clickhouse/schema";
export * from "./repositories/definitions";
export * from "../server/ingestion/types";
export * from "./ingestion/modelMatch";
export * from "./ingestion/processEventBatch";
export * from "../server/ingestion/types";
export * from "../server/ingestion/validateAndInflateScore";
export * from "./redis/redis";
export * from "./redis/traceUpsert";
export * from "./redis/cloudUsageMeteringQueue";
export * from "./redis/getQueue";
export * from "./redis/traceDelete";
export * from "./redis/projectDelete";
export * from "./redis/datasetRunItemUpsert";
export * from "./redis/batchExport";
export * from "./redis/legacyIngestion";
export * from "./redis/ingestionQueue";
export * from "./redis/postHogIntegrationQueue";
export * from "./redis/postHogIntegrationProcessingQueue";
export * from "./redis/experimentCreateQueue";
export * from "./auth/types";
export * from "./ingestion/legacy/index";
export * from "./queues";
@@ -32,3 +43,7 @@ export * from "./instrumentation";
export * from "./logger";
export * from "./queries";
export * from "./repositories";
export * from "./redis/evalExecutionQueue";
// test utils
export * from "./test-utils";
@@ -20,6 +20,9 @@ import { jsonSchema } from "../../../utils/zod";
import { prisma } from "../../../db";
import { LegacyIngestionAccessScope } from ".";
import { logger } from "../../logger";
import { env } from "../../../env";
import { upsertTrace } from "../../repositories";
import { convertDateToClickhouseDateTime } from "../../clickhouse/client";
export interface EventProcessor {
auth(apiScope: LegacyIngestionAccessScope): void;
@@ -131,22 +134,9 @@ export class ObservationProcessor implements EventProcessor {
})
: undefined;
const traceId =
!this.event.body.traceId && !existingObservation
? // Create trace if no traceid
(
await prisma.trace.create({
data: {
projectId: apiScope.projectId,
name: this.event.body.name,
},
})
).id
: this.event.body.traceId;
// Token counts
const [newInputCount, newOutputCount] =
"usage" in this.event.body
"usage" in this.event.body || "usageDetails" in this.event.body
? await this.calculateTokenCounts(
apiScope.projectId,
this.event.body,
@@ -157,8 +147,9 @@ export class ObservationProcessor implements EventProcessor {
: [undefined, undefined];
const newTotalCount =
"usage" in this.event.body
"usage" in this.event.body || "usageDetails" in this.event.body
? (this.event.body.usage?.total ??
this.event.body.usageDetails?.total ??
(newInputCount != null || newOutputCount != null
? (newInputCount ?? 0) + (newOutputCount ?? 0)
: undefined))
@@ -168,15 +159,24 @@ export class ObservationProcessor implements EventProcessor {
inputCost:
"usage" in this.event.body && this.event.body.usage?.inputCost != null // inputCost can be explicitly 0. Note only one equal sign to capture null AND undefined
? new Decimal(this.event.body.usage?.inputCost)
: existingObservation?.inputCost,
: "costDetails" in this.event.body &&
this.event.body.costDetails?.input != null
? new Decimal(this.event.body.costDetails?.input)
: existingObservation?.inputCost,
outputCost:
"usage" in this.event.body && this.event.body.usage?.outputCost != null // outputCost can be explicitly 0. Note only one equal sign to capture null AND undefined
? new Decimal(this.event.body.usage?.outputCost)
: existingObservation?.outputCost,
: "costDetails" in this.event.body &&
this.event.body.costDetails?.output != null
? new Decimal(this.event.body.costDetails?.output)
: existingObservation?.outputCost,
totalCost:
"usage" in this.event.body && this.event.body.usage?.totalCost != null // totalCost can be explicitly 0. Note only one equal sign to capture null AND undefined
? new Decimal(this.event.body.usage?.totalCost)
: existingObservation?.totalCost,
: "costDetails" in this.event.body &&
this.event.body.costDetails?.total != null
? new Decimal(this.event.body.costDetails?.total)
: existingObservation?.totalCost,
};
const tokenCounts = {
@@ -230,11 +230,46 @@ export class ObservationProcessor implements EventProcessor {
return newId;
})();
let traceId = this.event.body?.traceId;
if (!this.event.body.traceId && !existingObservation) {
// Create trace if no traceId
traceId = observationId;
// Insert trace into postgres
await prisma.trace.upsert({
where: {
id: observationId,
},
create: {
projectId: apiScope.projectId,
name: this.event.body.name,
id: observationId,
timestamp: this.event.body.startTime || new Date(),
},
update: {},
});
if (env.CLICKHOUSE_URL) {
// Insert trace into clickhouse if enabled
await upsertTrace({
id: observationId,
project_id: apiScope.projectId,
timestamp: convertDateToClickhouseDateTime(
this.event.body.startTime
? new Date(this.event.body.startTime)
: new Date(),
),
created_at: convertDateToClickhouseDateTime(new Date()),
updated_at: convertDateToClickhouseDateTime(new Date()),
});
}
}
return {
id: observationId,
create: {
id: observationId,
traceId: traceId,
traceId,
type: type,
name: this.event.body.name,
startTime: this.event.body.startTime
@@ -359,7 +394,7 @@ export class ObservationProcessor implements EventProcessor {
model?: Model,
existingObservation?: Omit<Observation, "input" | "output">,
) {
let newPromptTokens = body.usage?.input;
let newPromptTokens = body.usage?.input || body.usageDetails?.input;
if (newPromptTokens === undefined && model && model.tokenizerId) {
if (body.input) {
newPromptTokens = calculateTokenDelegate({
@@ -384,7 +419,7 @@ export class ObservationProcessor implements EventProcessor {
}
}
let newCompletionTokens = body.usage?.output;
let newCompletionTokens = body.usage?.output || body.usageDetails?.output;
if (newCompletionTokens === undefined && model && model.tokenizerId) {
if (body.output) {
@@ -600,19 +635,32 @@ export class TraceProcessor implements EventProcessor {
: undefined;
if (body.sessionId) {
await prisma.traceSession.upsert({
where: {
id_projectId: {
try {
await prisma.traceSession.upsert({
where: {
id_projectId: {
id: body.sessionId,
projectId: apiScope.projectId,
},
},
create: {
id: body.sessionId,
projectId: apiScope.projectId,
},
},
create: {
id: body.sessionId,
projectId: apiScope.projectId,
},
update: {},
});
update: {},
});
} catch (e) {
if (
e instanceof Prisma.PrismaClientKnownRequestError &&
e.code === "P2002"
) {
logger.warn(
`Failed to upsert session. Session ${body.sessionId} in project ${apiScope.projectId} already exists`,
);
} else {
throw e;
}
}
}
// Do not use nested upserts or multiple where conditions as this should be a single native database upsert
@@ -3,13 +3,7 @@ import z from "zod";
import { ForbiddenError, UnauthorizedError } from "../../../errors";
import { eventTypes, ingestionApiSchema, IngestionEventType } from "../types";
import { getProcessorForEvent } from "./EventProcessor";
import { TraceUpsertEventType } from "../../queues";
import {
convertTraceUpsertEventsToRedisEvents,
TraceUpsertQueue,
} from "../../redis/traceUpsert";
import { ApiAccessScope } from "../../auth/types";
import { redis } from "../../redis/redis";
import { backOff } from "exponential-backoff";
import { Model } from "../../..";
import { logger } from "../../logger";
@@ -89,7 +83,7 @@ export const handleBatch = async (
async function retry<T>(request: () => Promise<T>): Promise<T> {
return await backOff(request, {
numOfAttempts: env.LANGFUSE_ASYNC_INGESTION_PROCESSING === "true" ? 5 : 3,
numOfAttempts: 5,
retry: (e: Error, attemptNumber: number) => {
if (e instanceof UnauthorizedError || e instanceof ForbiddenError) {
logger.info("not retrying auth error");
@@ -163,42 +157,5 @@ export function cleanEvent(obj: unknown): unknown {
}
}
export const isNotNullOrUndefined = <T>(
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 addTracesToTraceUpsertQueue = async (
batchResults: BatchResult[],
projectId: string,
): Promise<void> => {
const traceEvents: TraceUpsertEventType[] = batchResults
.filter((result) => result.type === eventTypes.TRACE_CREATE) // we only have create, no update.
.map((result) =>
result.result &&
typeof result.result === "object" &&
"id" in result.result
? // ingestion API only gets traces for one projectId
{ traceId: result.result.id as string, projectId }
: null,
)
.filter(isNotNullOrUndefined);
try {
if (env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION && redis) {
logger.debug(`Sending ${traceEvents.length} events to worker via Redis`);
const queue = TraceUpsertQueue.getInstance();
if (!queue) {
logger.error("TraceUpsertQueue not initialized");
return;
}
await queue.addBulk(convertTraceUpsertEventsToRedisEvents(traceEvents));
}
} catch (error) {
logger.error("Error sending events to worker", error);
}
};
@@ -0,0 +1,426 @@
import { randomUUID } from "crypto";
import { z } from "zod";
import { type Model } from "../../db";
import { env } from "../../env";
import {
InvalidRequestError,
LangfuseNotFoundError,
UnauthorizedError,
} from "../../errors";
import { AuthHeaderValidVerificationResult } from "../auth/types";
import { getClickhouseEntityType } from "../clickhouse/schemaUtils";
import {
getCurrentSpan,
instrumentAsync,
instrumentSync,
recordIncrement,
traceException,
} from "../instrumentation";
import { logger } from "../logger";
import { LegacyIngestionEventType, QueueJobs } from "../queues";
import { IngestionQueue } from "../redis/ingestionQueue";
import { LegacyIngestionQueue } from "../redis/legacyIngestion";
import { redis } from "../redis/redis";
import { handleBatch } from "./legacy";
import {
StorageService,
StorageServiceFactory,
} from "../services/StorageService";
import { getProcessorForEvent } from "./legacy/EventProcessor";
import { eventTypes, ingestionEvent, IngestionEventType } from "./types";
export type TokenCountDelegate = (p: {
model: Model;
text: unknown;
}) => number | undefined;
let s3StorageServiceClient: StorageService;
const getS3StorageServiceClient = (bucketName: string): StorageService => {
if (!s3StorageServiceClient) {
s3StorageServiceClient = StorageServiceFactory.getInstance({
bucketName,
accessKeyId: env.LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID,
secretAccessKey: env.LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY,
endpoint: env.LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT,
region: env.LANGFUSE_S3_EVENT_UPLOAD_REGION,
forcePathStyle: env.LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE === "true",
});
}
return s3StorageServiceClient;
};
export const processEventBatch = async (
input: unknown[],
authCheck: AuthHeaderValidVerificationResult,
tokenCountDelegate: TokenCountDelegate,
): Promise<{
successes: { id: string; status: number }[];
errors: {
id: string;
status: number;
message?: string;
error?: string;
}[];
}> => {
// add context of api call to the span
const currentSpan = getCurrentSpan();
recordIncrement("langfuse.ingestion.event", input.length);
currentSpan?.setAttribute("event_count", input.length);
/**************
* VALIDATION *
**************/
const validationErrors: { id: string; error: unknown }[] = [];
const authenticationErrors: { id: string; error: unknown }[] = [];
const batch: z.infer<typeof ingestionEvent>[] = input
.flatMap((event) => {
const parsed = instrumentSync(
{ name: "ingestion-zod-parse-individual-event" },
(span) => {
const parsedBody = ingestionEvent.safeParse(event);
if (parsedBody.data?.id !== undefined) {
span.setAttribute("object.id", parsedBody.data.id);
}
return parsedBody;
},
);
if (!parsed.success) {
validationErrors.push({
id:
typeof event === "object" && event && "id" in event
? typeof event.id === "string"
? event.id
: "unknown"
: "unknown",
error: new InvalidRequestError(parsed.error.message),
});
return [];
}
if (!isAuthorized(parsed.data, authCheck, tokenCountDelegate)) {
authenticationErrors.push({
id: parsed.data.id,
error: new UnauthorizedError("Access Scope Denied"),
});
return [];
}
return [parsed.data];
})
.flatMap((event) => {
if (event.type === eventTypes.SDK_LOG) {
// Log SDK_LOG events, but remove them from further processing
logger.info("SDK Log Event", { event });
return [];
}
return [event];
});
const sortedBatch = sortBatch(batch);
// We group events by eventBodyId which allows us to store and process them
// as one which reduces infra interactions per event. Only used in the S3 case.
const sortedBatchByEventBodyId = sortedBatch.reduce(
(
acc: Record<
string,
{
data: IngestionEventType[];
key: string;
eventBodyId: string;
type: (typeof eventTypes)[keyof typeof eventTypes];
}
>,
event,
) => {
if (!event.body?.id) {
return acc;
}
const key = `${getClickhouseEntityType(event.type)}-${event.body.id}`;
if (!acc[key]) {
acc[key] = {
data: [],
key: event.id,
type: event.type,
eventBodyId: event.body.id,
};
}
acc[key].data.push(event);
return acc;
},
{},
);
/********************
* ASYNC PROCESSING *
********************/
let s3UploadErrored = false;
await instrumentAsync({ name: "s3-upload-events" }, async () => {
const s3Client = getS3StorageServiceClient(
env.LANGFUSE_S3_EVENT_UPLOAD_BUCKET,
);
// S3 Event Upload is blocking, but non-failing.
// If a promise rejects, we log it below, but do not throw an error.
// In this case, we upload the full batch into the Redis queue.
const results = await Promise.allSettled(
Object.keys(sortedBatchByEventBodyId).map(async (id) => {
// We upload the event in an array to the S3 bucket grouped by the eventBodyId.
// That way we batch updates from the same invocation into a single file and reduce
// write operations on S3.
const { data, key, type, eventBodyId } = sortedBatchByEventBodyId[id];
return s3Client.uploadJson(
`${env.LANGFUSE_S3_EVENT_UPLOAD_PREFIX}${authCheck.scope.projectId}/${getClickhouseEntityType(type)}/${eventBodyId}/${key}.json`,
data,
);
}),
);
results.forEach((result) => {
if (result.status === "rejected") {
s3UploadErrored = true;
logger.error("Failed to upload event to S3", {
error: result.reason,
});
}
});
});
// This is a workaround to allow us to disable async ingestion processing for SDK CI testing
// TODO: remove this block after SDKs are ready for V3 async ingestion processing
if (env.LANGFUSE_SDK_CI_SYNC_PROCESSING_ENABLED === "true") {
const result = await handleBatch(
sortedBatch,
authCheck,
tokenCountDelegate,
);
// in case we did not return early, we return the result here
return aggregateBatchResult(
[...validationErrors, ...authenticationErrors, ...result.errors],
result.results,
authCheck.scope.projectId,
);
}
// Send each event individually to IngestionQueue for ClickHouse processing
if (env.LANGFUSE_CLICKHOUSE_INGESTION_ENABLED === "true") {
if (s3UploadErrored) {
throw new Error(
"Failed to upload events to blob storage, aborting event processing",
);
}
if (redis) {
const queue = IngestionQueue.getInstance();
await Promise.all(
Object.keys(sortedBatchByEventBodyId).map(async (id) =>
queue
? queue.add(
QueueJobs.IngestionJob,
{
id: randomUUID(),
timestamp: new Date(),
name: QueueJobs.IngestionJob as const,
payload: {
data: {
type: sortedBatchByEventBodyId[id].type,
eventBodyId: sortedBatchByEventBodyId[id].eventBodyId,
},
authCheck,
},
},
{
delay: env.LANGFUSE_INGESTION_QUEUE_DELAY_MS,
},
)
: Promise.reject("Failed to instantiate queue"),
),
);
if (env.LANGFUSE_POSTGRES_INGESTION_ENABLED !== "true") {
// If postgres ingestion is disabled, we return early
return aggregateBatchResult(
[...validationErrors, ...authenticationErrors],
sortedBatch.map((event) => ({ id: event.id, result: event })),
authCheck.scope.projectId,
);
}
}
}
if (env.LANGFUSE_POSTGRES_INGESTION_ENABLED === "true") {
// As part of the legacy processing we sent the entire batch to the worker.
if (redis) {
const queue = LegacyIngestionQueue.getInstance();
if (queue) {
let addToQueueFailed = false;
const queuePayload: LegacyIngestionEventType = !s3UploadErrored
? {
data: Object.keys(sortedBatchByEventBodyId).map((id) => {
const { key, type, eventBodyId } = sortedBatchByEventBodyId[id];
return {
type,
eventBodyId,
eventId: key,
};
}),
authCheck,
useS3EventStore: true,
}
: { data: sortedBatch, authCheck, useS3EventStore: false };
try {
await queue.add(QueueJobs.LegacyIngestionJob, {
payload: queuePayload,
id: randomUUID(),
timestamp: new Date(),
name: QueueJobs.LegacyIngestionJob as const,
});
} catch (e: unknown) {
logger.warn(
"Failed to add batch to queue, falling back to sync processing",
e,
);
addToQueueFailed = true;
}
if (!addToQueueFailed) {
return aggregateBatchResult(
// we are not sending additional server errors to the client in case of early return
[...validationErrors, ...authenticationErrors],
sortedBatch.map((event) => ({ id: event.id, result: event })),
authCheck.scope.projectId,
);
}
} else {
logger.warn(
"Ingestion queue not initialized, falling back to sync processing",
);
}
}
/*******************
* SYNC PROCESSING *
*******************/
const result = await handleBatch(
sortedBatch,
authCheck,
tokenCountDelegate,
);
// in case we did not return early, we return the result here
return aggregateBatchResult(
[...validationErrors, ...authenticationErrors, ...result.errors],
result.results,
authCheck.scope.projectId,
);
}
throw new Error(
"Either Clickhouse or Postgres ingestion (or both) must be enabled",
);
};
const isAuthorized = (
event: IngestionEventType,
authScope: AuthHeaderValidVerificationResult,
tokenCountDelegate: TokenCountDelegate,
): boolean => {
try {
getProcessorForEvent(event, tokenCountDelegate).auth(authScope.scope);
return true;
} catch (error) {
return false;
}
};
/**
* Sorts a batch of ingestion events. Orders by: updating events last, sorted by timestamp asc.
*/
const sortBatch = (batch: Array<z.infer<typeof ingestionEvent>>) => {
const updateEvents: (typeof eventTypes)[keyof typeof eventTypes][] = [
eventTypes.GENERATION_UPDATE,
eventTypes.SPAN_UPDATE,
eventTypes.OBSERVATION_UPDATE, // legacy event type
];
const updates = batch
.filter((event) => updateEvents.includes(event.type))
.sort((a, b) => {
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
});
const others = batch
.filter((event) => !updateEvents.includes(event.type))
.sort((a, b) => {
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
});
// Return the array with non-update events first, followed by update events
return [...others, ...updates];
};
export const aggregateBatchResult = (
errors: Array<{ id: string; error: unknown }>,
results: Array<{ id: string; result: unknown }>,
projectId?: string,
) => {
const returnedErrors: {
id: string;
status: number;
message?: string;
error?: string;
}[] = [];
const successes: {
id: string;
status: number;
}[] = [];
errors.forEach((error) => {
if (error.error instanceof InvalidRequestError) {
returnedErrors.push({
id: error.id,
status: 400,
message: "Invalid request data",
error: error.error.message,
});
} else if (error.error instanceof UnauthorizedError) {
returnedErrors.push({
id: error.id,
status: 401,
message: "Authentication error",
error: error.error.message,
});
} else if (error.error instanceof LangfuseNotFoundError) {
returnedErrors.push({
id: error.id,
status: 404,
message: "Resource not found",
error: error.error.message,
});
} else {
returnedErrors.push({
id: error.id,
status: 500,
error: "Internal Server Error",
});
}
});
if (returnedErrors.length > 0) {
traceException(errors);
logger.error("Error processing events", {
errors: returnedErrors,
"langfuse.project.id": projectId,
});
}
results.forEach((result) => {
successes.push({
id: result.id,
status: 201,
});
});
return { successes, errors: returnedErrors };
};
+63 -1
View File
@@ -56,10 +56,66 @@ export const usage = MixedUsage.nullish()
// ensure output is always of new usage model
.pipe(Usage.nullish());
const RawUsageOrCostDetails = z.record(
z.string(),
z.number().nonnegative().nullish(),
);
const OpenAIUsageSchema = z
.object({
prompt_tokens: z.number().nonnegative(),
completion_tokens: z.number().nonnegative(),
total_tokens: z.number().nonnegative(),
prompt_tokens_details: z.record(z.string(), z.number().nonnegative()),
completion_tokens_details: z.record(z.string(), z.number().nonnegative()),
})
.strict()
.transform((v) => {
if (!v) return;
const {
prompt_tokens,
completion_tokens,
total_tokens,
prompt_tokens_details,
completion_tokens_details,
} = v;
const result: z.infer<typeof RawUsageOrCostDetails> & {
input: number;
output: number;
total: number;
} = {
input: prompt_tokens,
output: completion_tokens,
total: total_tokens,
};
if (prompt_tokens_details) {
for (const [key, value] of Object.entries(prompt_tokens_details)) {
result[`input_${key}`] = value;
result.input = Math.max(result.input - (value ?? 0), 0);
}
}
if (completion_tokens_details) {
for (const [key, value] of Object.entries(completion_tokens_details)) {
result[`output_${key}`] = value;
result.output = Math.max(result.output - (value ?? 0), 0);
}
}
return result;
})
.pipe(RawUsageOrCostDetails);
export const UsageOrCostDetails = z
.union([OpenAIUsageSchema, RawUsageOrCostDetails])
.nullish();
export const TraceBody = z.object({
id: z.string().nullish(),
timestamp: stringDateTime,
name: z.string().nullish(),
name: z.string().max(1000).nullish(),
externalId: z.string().nullish(),
input: jsonSchema.nullish(),
output: jsonSchema.nullish(),
@@ -119,6 +175,8 @@ export const CreateGenerationBody = CreateSpanBody.extend({
)
.nullish(),
usage: usage,
usageDetails: UsageOrCostDetails,
costDetails: UsageOrCostDetails,
promptName: z.string().nullish(),
promptVersion: z.number().int().nullish(),
}).refine((value) => {
@@ -147,6 +205,8 @@ export const UpdateGenerationBody = UpdateSpanBody.extend({
)
.nullish(),
usage: usage,
usageDetails: UsageOrCostDetails,
costDetails: UsageOrCostDetails,
promptName: z.string().nullish(),
promptVersion: z.number().int().nullish(),
}).refine((value) => {
@@ -298,6 +358,8 @@ export const LegacyObservationBody = z.object({
input: jsonSchema.nullish(),
output: jsonSchema.nullish(),
usage: usage,
usageDetails: UsageOrCostDetails,
costDetails: UsageOrCostDetails,
metadata: jsonSchema.nullish(),
parentObservationId: z.string().nullish(),
level: z.nativeEnum(ObservationLevel).nullish(),
@@ -17,7 +17,7 @@ type ValidateAndInflateScoreParams = {
export async function validateAndInflateScore(
params: ValidateAndInflateScoreParams,
): Promise<Score> {
const { body, projectId } = params;
const { body, projectId, scoreId } = params;
if (body.configId) {
const config = await prisma.scoreConfig.findFirst({
@@ -32,10 +32,22 @@ export async function validateAndInflateScore(
"The configId you provided does not match a valid config in this project",
);
validateConfigAgainstBody(body, config as ValidatedScoreConfig);
// Override some fields in the score body with config fields
// We ignore the set fields in the body
const bodyWithConfigOverrides = {
...body,
name: config.name,
};
validateConfigAgainstBody(
bodyWithConfigOverrides,
config as ValidatedScoreConfig,
);
return inflateScoreBody({
...params,
projectId,
scoreId,
body: bodyWithConfigOverrides,
config: config as ValidatedScoreConfig,
});
}
@@ -1,10 +1,10 @@
import * as opentelemetry from "@opentelemetry/api";
import * as dd from "dd-trace";
import { env } from "../../env";
import {
CloudWatchClient,
PutMetricDataCommand,
} from "@aws-sdk/client-cloudwatch";
import * as opentelemetry from "@opentelemetry/api";
import * as dd from "dd-trace";
import { env } from "../../env";
import { logger } from "../logger";
// type CallbackFn<T> = () => T;
@@ -1,9 +1,13 @@
import type { ZodSchema } from "zod";
import { CallbackHandler } from "langfuse-langchain";
import { ChatAnthropic } from "@langchain/anthropic";
import { ChatVertexAI } from "@langchain/google-vertexai";
import { ChatBedrockConverse } from "@langchain/aws";
import {
AIMessage,
BaseMessage,
HumanMessage,
SystemMessage,
} from "@langchain/core/messages";
@@ -13,15 +17,31 @@ import {
} from "@langchain/core/output_parsers";
import { IterableReadableStream } from "@langchain/core/utils/stream";
import { ChatOpenAI } from "@langchain/openai";
import {
import GCPServiceAccountKeySchema, {
BedrockConfigSchema,
BedrockCredentialSchema,
} from "../../interfaces/customLLMProviderConfigSchemas";
import { AuthHeaderValidVerificationResult } from "../auth/types";
import {
processEventBatch,
type TokenCountDelegate,
} from "../ingestion/processEventBatch";
import { logger } from "../logger";
import { ChatMessage, ChatMessageRole, LLMAdapter, ModelParams } from "./types";
import type { BaseCallbackHandler } from "@langchain/core/callbacks/base";
type ProcessTracedEvents = () => Promise<void>;
export type TraceParams = {
traceName: string;
traceId: string;
projectId: string;
tags: string[];
tokenCountDelegate: TokenCountDelegate;
authCheck: AuthHeaderValidVerificationResult;
};
type LLMCompletionParams = {
messages: ChatMessage[];
modelParams: ModelParams;
@@ -31,6 +51,7 @@ type LLMCompletionParams = {
apiKey: string;
maxRetries?: number;
config?: Record<string, string> | null;
traceParams?: TraceParams;
};
type FetchLLMCompletionParams = LLMCompletionParams & {
@@ -40,25 +61,34 @@ type FetchLLMCompletionParams = LLMCompletionParams & {
export async function fetchLLMCompletion(
params: LLMCompletionParams & {
streaming: true;
}
): Promise<IterableReadableStream<Uint8Array>>;
},
): Promise<{
completion: IterableReadableStream<Uint8Array>;
processTracedEvents: ProcessTracedEvents;
}>;
export async function fetchLLMCompletion(
params: LLMCompletionParams & {
streaming: false;
}
): Promise<string>;
},
): Promise<{ completion: string; processTracedEvents: ProcessTracedEvents }>;
export async function fetchLLMCompletion(
params: LLMCompletionParams & {
streaming: false;
structuredOutputSchema: ZodSchema;
}
): Promise<unknown>;
},
): Promise<{
completion: unknown;
processTracedEvents: ProcessTracedEvents;
}>;
export async function fetchLLMCompletion(
params: FetchLLMCompletionParams
): Promise<string | IterableReadableStream<Uint8Array> | unknown> {
params: FetchLLMCompletionParams,
): Promise<{
completion: string | IterableReadableStream<Uint8Array> | unknown;
processTracedEvents: ProcessTracedEvents;
}> {
// the apiKey must never be printed to the console
const {
messages,
@@ -69,18 +99,61 @@ export async function fetchLLMCompletion(
baseURL,
maxRetries,
config,
traceParams,
} = params;
const finalMessages = messages.map((message) => {
if (message.role === ChatMessageRole.User)
return new HumanMessage(message.content);
if (message.role === ChatMessageRole.System)
return new SystemMessage(message.content);
let finalCallbacks: BaseCallbackHandler[] | undefined = callbacks ?? [];
let processTracedEvents: ProcessTracedEvents = () => Promise.resolve();
return new AIMessage(message.content);
});
if (traceParams) {
const handler = new CallbackHandler({
_projectId: traceParams.projectId,
_isLocalEventExportEnabled: true,
tags: traceParams.tags,
});
let chatModel: ChatOpenAI | ChatAnthropic | ChatBedrockConverse;
finalCallbacks.push(handler);
processTracedEvents = async () => {
try {
const events = await handler.langfuse._exportLocalEvents(
traceParams.projectId,
);
await processEventBatch(
JSON.parse(JSON.stringify(events)), // stringify to emulate network event batch from network call
traceParams.authCheck,
traceParams.tokenCountDelegate,
);
} catch (e) {
logger.error("Failed to process traced events", { error: e });
}
};
}
finalCallbacks = finalCallbacks.length > 0 ? finalCallbacks : undefined;
let finalMessages: BaseMessage[];
// VertexAI requires at least 1 user message
if (modelParams.adapter === LLMAdapter.VertexAI && messages.length === 1) {
finalMessages = [new HumanMessage(messages[0].content)];
} else {
finalMessages = messages.map((message) => {
if (message.role === ChatMessageRole.User)
return new HumanMessage(message.content);
if (message.role === ChatMessageRole.System)
return new SystemMessage(message.content);
return new AIMessage(message.content);
});
}
finalMessages = finalMessages.filter((m) => m.content.length > 0);
let chatModel:
| ChatOpenAI
| ChatAnthropic
| ChatBedrockConverse
| ChatVertexAI;
if (modelParams.adapter === LLMAdapter.Anthropic) {
chatModel = new ChatAnthropic({
anthropicApiKey: apiKey,
@@ -89,8 +162,8 @@ export async function fetchLLMCompletion(
temperature: modelParams.temperature,
maxTokens: modelParams.max_tokens,
topP: modelParams.top_p,
callbacks,
clientOptions: { maxRetries },
callbacks: finalCallbacks,
clientOptions: { maxRetries, timeout: 1000 * 60 * 2 }, // 2 minutes timeout
});
} else if (modelParams.adapter === LLMAdapter.OpenAI) {
chatModel = new ChatOpenAI({
@@ -99,11 +172,13 @@ export async function fetchLLMCompletion(
temperature: modelParams.temperature,
maxTokens: modelParams.max_tokens,
topP: modelParams.top_p,
callbacks,
streamUsage: false, // https://github.com/langchain-ai/langchainjs/issues/6533
callbacks: finalCallbacks,
maxRetries,
configuration: {
baseURL,
},
timeout: 1000 * 60 * 2, // 2 minutes timeout
});
} else if (modelParams.adapter === LLMAdapter.Azure) {
chatModel = new ChatOpenAI({
@@ -114,8 +189,9 @@ export async function fetchLLMCompletion(
temperature: modelParams.temperature,
maxTokens: modelParams.max_tokens,
topP: modelParams.top_p,
callbacks,
callbacks: finalCallbacks,
maxRetries,
timeout: 1000 * 60 * 2, // 2 minutes timeout
});
} else if (modelParams.adapter === LLMAdapter.Bedrock) {
const { region } = BedrockConfigSchema.parse(config);
@@ -128,8 +204,26 @@ export async function fetchLLMCompletion(
temperature: modelParams.temperature,
maxTokens: modelParams.max_tokens,
topP: modelParams.top_p,
callbacks,
callbacks: finalCallbacks,
maxRetries,
timeout: 1000 * 60 * 2, // 2 minutes timeout
});
} else if (modelParams.adapter === LLMAdapter.VertexAI) {
const credentials = GCPServiceAccountKeySchema.parse(JSON.parse(apiKey));
// Requests time out after 60 seconds for both public and private endpoints by default
// Reference: https://cloud.google.com/vertex-ai/docs/predictions/get-online-predictions#send-request
chatModel = new ChatVertexAI({
modelName: modelParams.model,
temperature: modelParams.temperature,
maxOutputTokens: modelParams.max_tokens,
topP: modelParams.top_p,
callbacks: finalCallbacks,
maxRetries,
authOptions: {
projectId: credentials.project_id,
credentials,
},
});
} else {
// eslint-disable-next-line no-unused-vars
@@ -137,10 +231,19 @@ export async function fetchLLMCompletion(
throw new Error("This model provider is not supported.");
}
const runConfig = {
callbacks: finalCallbacks,
runId: traceParams?.traceId,
runName: traceParams?.traceName,
};
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);
return {
completion: await (chatModel as ChatOpenAI) // Typecast necessary due to https://github.com/langchain-ai/langchainjs/issues/6795
.withStructuredOutput(params.structuredOutputSchema)
.invoke(finalMessages, runConfig),
processTracedEvents,
};
}
/*
@@ -156,27 +259,42 @@ export async function fetchLLMCompletion(
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")
);
return {
completion: await new ChatOpenAI({
openAIApiKey: apiKey,
modelName: modelParams.model,
temperature: 1,
maxTokens: undefined,
topP: undefined,
callbacks,
maxRetries,
configuration: {
baseURL,
},
timeout: 1000 * 60 * 2, // 2 minutes timeout
})
.pipe(new StringOutputParser())
.invoke(
finalMessages.filter((message) => message._getType() !== "system"),
runConfig,
),
processTracedEvents,
};
}
if (streaming) {
return chatModel.pipe(new BytesOutputParser()).stream(finalMessages);
return {
completion: await chatModel
.pipe(new BytesOutputParser())
.stream(finalMessages, runConfig),
processTracedEvents,
};
}
return await chatModel.pipe(new StringOutputParser()).invoke(finalMessages);
return {
completion: await chatModel
.pipe(new StringOutputParser())
.invoke(finalMessages, runConfig),
processTracedEvents,
};
}
+34
View File
@@ -16,6 +16,7 @@ export enum LLMAdapter {
OpenAI = "openai",
Azure = "azure",
Bedrock = "bedrock",
VertexAI = "vertex-ai",
}
export enum ChatMessageRole {
@@ -26,6 +27,20 @@ export enum ChatMessageRole {
export const ChatMessageDefaultRoleSchema = z.nativeEnum(ChatMessageRole);
const ChatMessageSchema = z.object({
role: z.union([ChatMessageDefaultRoleSchema, z.string()]), // Users may ingest any string as role via API/SDK
content: z.string(),
});
export const ChatMessageListSchema = z.array(ChatMessageSchema);
export const TextPromptSchema = z.string().min(1, "Enter a prompt");
export const PromptContentSchema = z.union([
ChatMessageListSchema,
TextPromptSchema,
]);
export type PromptContent = z.infer<typeof PromptContentSchema>;
export type ModelParams = {
provider: string;
adapter: LLMAdapter;
@@ -49,6 +64,17 @@ export const ZodModelConfig = z.object({
top_p: z.coerce.number().optional(),
});
// Experiment config
export const ExperimentMetadataSchema = z
.object({
prompt_id: z.string(),
provider: z.string(),
model: z.string(),
model_params: ZodModelConfig,
})
.strict();
export type ExperimentMetadata = z.infer<typeof ExperimentMetadataSchema>;
// NOTE: Update docs page when changing this! https://langfuse.com/docs/playground#openai-playground--anthropic-playground
export const openAIModels = [
"gpt-4o",
@@ -89,10 +115,18 @@ export const anthropicModels = [
"claude-instant-1.2",
] as const;
export const vertexAIModels = [
"gemini-1.5-pro",
"gemini-1.5-flash",
"gemini-1.0-pro",
] as const;
export type AnthropicModel = (typeof anthropicModels)[number];
export type VertexAIModel = (typeof vertexAIModels)[number];
export const supportedModels = {
[LLMAdapter.Anthropic]: anthropicModels,
[LLMAdapter.OpenAI]: openAIModels,
[LLMAdapter.VertexAI]: vertexAIModels,
[LLMAdapter.Azure]: [],
[LLMAdapter.Bedrock]: [],
} as const;
@@ -1,20 +1,13 @@
import { filterOperators } from "../../../interfaces/filters";
import { clickhouseCompliantRandomCharacters } from "../../repositories";
function randomCharacters() {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let result = "";
const randomArray = new Uint8Array(5);
crypto.getRandomValues(randomArray);
randomArray.forEach((number) => {
result += chars[number % chars.length];
});
return result;
}
export type ClickhouseOperator =
| (typeof filterOperators)[keyof typeof filterOperators][number]
| "!=";
export interface Filter {
apply(): ClickhouseFilter;
clickhouseTable: string;
operator: (typeof filterOperators)[keyof typeof filterOperators][number];
operator: ClickhouseOperator;
field: string;
}
type ClickhouseFilter = {
@@ -44,7 +37,7 @@ export class StringFilter implements Filter {
}
apply(): ClickhouseFilter {
const varName = `stringFilter${randomCharacters()}`;
const varName = `stringFilter${clickhouseCompliantRandomCharacters()}`;
const fieldWithPrefix = `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}`;
let query: string;
@@ -79,29 +72,33 @@ export class NumberFilter implements Filter {
public clickhouseTable: string;
public field: string;
public value: number;
public operator: (typeof filterOperators)["number"][number];
public operator: (typeof filterOperators)["number"][number] | "!=";
public clickhouseTypeOverwrite?: string;
protected tablePrefix?: string;
constructor(opts: {
clickhouseTable: string;
field: string;
operator: (typeof filterOperators)["number"][number];
operator: (typeof filterOperators)["number"][number] | "!=";
value: number;
tablePrefix?: string;
clickhouseTypeOverwrite?: string;
}) {
this.clickhouseTable = opts.clickhouseTable;
this.field = opts.field;
this.value = opts.value;
this.operator = opts.operator;
this.tablePrefix = opts.tablePrefix;
this.clickhouseTypeOverwrite = opts.clickhouseTypeOverwrite;
}
apply(): ClickhouseFilter {
const uid = randomCharacters();
const uid = clickhouseCompliantRandomCharacters();
const varName = `numberFilter${uid}`;
const type = this.clickhouseTypeOverwrite ?? "Decimal64(12)";
return {
query: `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field} ${this.operator} {${varName}: Decimal}`,
params: { [varName]: this.value },
query: `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field} ${this.operator} {${varName}: ${type}}`,
params: { [varName]: this.value.toString() },
};
}
}
@@ -128,7 +125,7 @@ export class DateTimeFilter implements Filter {
}
apply(): ClickhouseFilter {
const uid = randomCharacters();
const uid = clickhouseCompliantRandomCharacters();
const varName = `dateTimeFilter${uid}`;
return {
query: `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field} ${this.operator} {${varName}: DateTime64(3)}`,
@@ -159,13 +156,13 @@ export class StringOptionsFilter implements Filter {
}
apply(): ClickhouseFilter {
const uid = randomCharacters();
const uid = clickhouseCompliantRandomCharacters();
const varName = `stringOptionsFilter${uid}`;
return {
query:
this.operator === "any of"
? `has({${varName}: Array(String)}, ${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}) = True`
: `has({${varName}: Array(String)}, ${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}) = False`,
? `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field} IN ({${varName}: Array(String)})`
: `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field} NOT IN ({${varName}: Array(String)})`,
params: { [varName]: this.values },
};
}
@@ -198,8 +195,8 @@ export class StringObjectFilter implements Filter {
}
apply(): ClickhouseFilter {
const varKeyName = `stringObjectKeyFilter${randomCharacters()}`;
const varValueName = `stringObjectValueFilter${randomCharacters()}`;
const varKeyName = `stringObjectKeyFilter${clickhouseCompliantRandomCharacters()}`;
const varValueName = `stringObjectValueFilter${clickhouseCompliantRandomCharacters()}`;
const column = `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}`;
// const query: `${column}['{varKeyName: String}'] ${this.operator} {${varValueName}: String}`,
@@ -254,7 +251,7 @@ export class ArrayOptionsFilter implements Filter {
}
apply(): ClickhouseFilter {
const uid = randomCharacters();
const uid = clickhouseCompliantRandomCharacters();
const varName = `arrayOptionsFilter${uid}`;
let query: string;
@@ -266,7 +263,7 @@ export class ArrayOptionsFilter implements Filter {
query = `hasAny({${varName}: Array(String)}, ${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}) = False`;
break;
case "all of":
query = `arrayAll(x -> has({${varName}: Array(String)}, x), ${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}) = True`;
query = `hasAll(${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}, {${varName}: Array(String)}) = True`;
break;
default:
throw new Error(`Unsupported operator: ${this.operator}`);
@@ -279,18 +276,44 @@ export class ArrayOptionsFilter implements Filter {
}
}
export class NumberObjectFilter implements Filter {
export class NullFilter implements Filter {
public clickhouseTable: string;
public field: string;
public key: string;
public value: number;
public operator: (typeof filterOperators)["numberObject"][number];
public operator: (typeof filterOperators)["null"][number];
protected tablePrefix?: string;
constructor(opts: {
clickhouseTable: string;
field: string;
operator: (typeof filterOperators)["numberObject"][number];
operator: (typeof filterOperators)["null"][number];
tablePrefix?: string;
}) {
this.clickhouseTable = opts.clickhouseTable;
this.field = opts.field;
this.operator = opts.operator;
this.tablePrefix = opts.tablePrefix;
}
apply(): ClickhouseFilter {
return {
query: `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field} ${this.operator}`,
params: {},
};
}
}
export class NumberObjectFilter implements Filter {
public clickhouseTable: string;
public field: string;
public key: string;
public value: number;
public operator: (typeof filterOperators)["numberObject"][number] | "!=";
protected tablePrefix?: string;
constructor(opts: {
clickhouseTable: string;
field: string;
operator: (typeof filterOperators)["numberObject"][number] | "!=";
key: string;
value: number;
tablePrefix?: string;
@@ -304,11 +327,11 @@ export class NumberObjectFilter implements Filter {
}
apply(): ClickhouseFilter {
const varKeyName = `numberObjectKeyFilter${randomCharacters()}`;
const varValueName = `numberObjectValueFilter${randomCharacters()}`;
const varKeyName = `numberObjectKeyFilter${clickhouseCompliantRandomCharacters()}`;
const varValueName = `numberObjectValueFilter${clickhouseCompliantRandomCharacters()}`;
const column = `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field}`;
return {
query: `empty(arrayFilter(x -> (((x.1) = {${varKeyName}: String}) AND ((x.2) ${this.operator} {${varValueName}: Decimal})), ${column})) = 0`,
query: `empty(arrayFilter(x -> (((x.1) = {${varKeyName}: String}) AND ((x.2) ${this.operator} {${varValueName}: Decimal64(12)})), ${column})) = 0`,
params: { [varKeyName]: this.key, [varValueName]: this.value },
};
}
@@ -336,7 +359,7 @@ export class BooleanFilter implements Filter {
}
apply(): ClickhouseFilter {
const uid = randomCharacters();
const uid = clickhouseCompliantRandomCharacters();
const varName = `booleanFilter${uid}`;
return {
query: `${this.tablePrefix ? this.tablePrefix + "." : ""}${this.field} ${this.operator} {${varName}: Boolean}`,
@@ -348,7 +371,7 @@ export class BooleanFilter implements Filter {
export class FilterList {
private filters: Filter[];
constructor(filters: Filter[]) {
constructor(filters: Filter[] = []) {
this.filters = filters;
}
@@ -360,6 +383,10 @@ export class FilterList {
return this.filters.find(predicate);
}
length() {
return this.filters.length;
}
public apply(): ClickhouseFilter {
if (this.filters.length === 0) {
return {
@@ -14,6 +14,7 @@ import {
BooleanFilter,
NumberObjectFilter,
StringObjectFilter,
NullFilter,
} from "./clickhouse-filter";
export class QueryBuilderError extends Error {
@@ -66,6 +67,7 @@ export const createFilterFromFilterState = (
operator: frontEndFilter.operator,
value: frontEndFilter.value,
tablePrefix: column.queryPrefix,
clickhouseTypeOverwrite: column.clickhouseTypeOverwrite,
});
case "arrayOptions":
return new ArrayOptionsFilter({
@@ -101,9 +103,16 @@ export const createFilterFromFilterState = (
value: frontEndFilter.value,
tablePrefix: column.queryPrefix,
});
case "null":
return new NullFilter({
clickhouseTable: column.clickhouseTableName,
field: column.clickhouseSelect,
operator: frontEndFilter.operator,
tablePrefix: column.queryPrefix,
});
default:
logger.error(`Invalid filter type: ${JSON.stringify(frontEndFilter)}`);
const exhaustiveCheck: never = frontEndFilter;
logger.error(`Invalid filter type: ${JSON.stringify(exhaustiveCheck)}`);
throw new QueryBuilderError(`Invalid filter type`);
}
});
@@ -3,31 +3,53 @@ import { OrderByState } from "../../../interfaces/orderBy";
import { UiColumnMapping } from "../../../tableDefinitions";
import { logger } from "../../logger";
type OrderByStateNotNull = Exclude<OrderByState, null>;
export function orderByToClickhouseSql(
orderBy: OrderByState,
orderBy: OrderByState | OrderByState[] = [],
tableColumns: UiColumnMapping[],
): string {
if (!orderBy) {
if (
!orderBy ||
(Array.isArray(orderBy) && orderBy.filter(Boolean).length === 0)
) {
return "";
}
// Get column definition to map column to internal name, e.g. "t.id"
const col = tableColumns.find(
(c) => c.uiTableName === orderBy.column || c.uiTableId === orderBy.column,
);
if (!col) {
logger.warn("Invalid order by column", orderBy.column);
throw new Error("Invalid order by column: " + orderBy.column);
if (!Array.isArray(orderBy)) {
orderBy = [orderBy];
}
// Initialize an array to hold order by clauses
const orderByClauses: string[] = [];
// Loop through each orderBy entry
for (const ob of orderBy.filter((o): o is OrderByStateNotNull =>
Boolean(o),
)) {
// Get column definition to map column to internal name, e.g. "t.id"
const col = tableColumns.find(
(c) => c.uiTableName === ob.column || c.uiTableId === ob.column,
);
if (!col) {
logger.warn("Invalid order by column", ob.column);
throw new Error("Invalid order by column: " + ob.column);
}
// Assert that ob.order is either "asc" or "desc"
const orderByOrder = z.enum(["ASC", "DESC"]);
const order = orderByOrder.safeParse(ob.order);
if (!order.success) {
logger.warn("Invalid order", ob.order);
throw new Error("Invalid order: " + ob.order);
}
// Append the order by clause to the array
orderByClauses.push(
`${col.queryPrefix ? col.queryPrefix + "." : ""}${col.clickhouseSelect} ${order.data}`,
);
}
// Assert that orderBy.order is either "asc" or "desc"
const orderByOrder = z.enum(["ASC", "DESC"]);
const order = orderByOrder.safeParse(orderBy.order);
if (!order.success) {
logger.warn("Invalid order", orderBy.order);
throw new Error("Invalid order: " + orderBy.order);
}
// Both column and order are safe, can use raw SQL
return `ORDER BY ${col.queryPrefix ? col.queryPrefix + "." : ""}${col.clickhouseSelect} ${order.data}`;
// Join all order by clauses with a comma and return
return `ORDER BY ${orderByClauses.join(", ")}`;
}
@@ -10,6 +10,8 @@ import { TableFilters } from "./types";
type AdditionalObservationFields = {
traceName: string | null;
traceTags: Array<string>;
usageDetails: Record<string, number>;
costDetails: Record<string, number>;
};
export type FullObservation = AdditionalObservationFields & ObservationView;
@@ -11,13 +11,13 @@ export function parseTraceAllFilters(input: TableFilters) {
const filterCondition = tableColumnsToSqlFilterAndPrefix(
input.filter ?? [],
tracesTableCols,
"traces"
"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"
(f) => f.column === "Timestamp" && f.type === "datetime",
);
const observationTimeseriesFilter =
@@ -25,7 +25,7 @@ export function parseTraceAllFilters(input: TableFilters) {
? datetimeFilterToPrismaSql(
"start_time",
timeseriesFilter.operator,
timeseriesFilter.value
timeseriesFilter.value,
)
: Prisma.empty;
@@ -130,6 +130,6 @@ export function createTracesQuery({
${filterCondition}
${orderByCondition}
${limit ? Prisma.sql`LIMIT ${limit}` : Prisma.empty}
${page && limit ? Prisma.sql`OFFSET ${page * limit}` : Prisma.empty}
${page !== undefined && limit !== undefined ? Prisma.sql`OFFSET ${page * limit}` : Prisma.empty}
`;
}
@@ -7,3 +7,17 @@ export {
type FullObservationsWithScores,
type IOAndMetadataOmittedObservations,
} from "./createGenerationsQuery";
export {
FilterList,
StringFilter,
DateTimeFilter,
StringOptionsFilter,
NumberFilter,
ArrayOptionsFilter,
BooleanFilter,
NumberObjectFilter,
StringObjectFilter,
NullFilter,
type ClickhouseOperator,
} from "./clickhouse-sql/clickhouse-filter";
export { orderByToClickhouseSql } from "./clickhouse-sql/orderby-factory";
+87 -28
View File
@@ -1,14 +1,6 @@
import { z } from "zod";
import { eventTypes, ingestionBatchEvent } from ".";
export enum EventName {
TraceUpsert = "TraceUpsert",
BatchExport = "BatchExport",
EvaluationExecution = "EvaluationExecution",
LegacyIngestion = "LegacyIngestion",
CloudUsageMetering = "CloudUsageMetering",
}
export const LegacyIngestionEventFull = z.object({
useS3EventStore: z.literal(false),
data: ingestionBatchEvent,
@@ -62,64 +54,113 @@ export const BatchExportJobSchema = z.object({
projectId: z.string(),
batchExportId: z.string(),
});
export const TraceUpsertEventSchema = z.object({
export const TraceQueueEventSchema = z.object({
projectId: z.string(),
traceId: z.string(),
});
export const TracesQueueEventSchema = z.object({
projectId: z.string(),
traceIds: z.array(z.string()),
});
export const ProjectQueueEventSchema = z.object({
projectId: z.string(),
orgId: z.string(),
});
export const DatasetRunItemUpsertEventSchema = z.object({
projectId: z.string(),
datasetItemId: z.string(),
traceId: z.string(),
observationId: z.string().optional(),
});
export const EvalExecutionEvent = z.object({
projectId: z.string(),
jobExecutionId: z.string(),
delay: z.number().nullish(),
});
export const PostHogIntegrationProcessingEventSchema = z.object({
projectId: z.string(),
});
export const ExperimentCreateEventSchema = z.object({
projectId: z.string(),
datasetId: z.string(),
runId: z.string(),
description: z.string().optional(),
});
export type BatchExportJobType = z.infer<typeof BatchExportJobSchema>;
export type TraceUpsertEventType = z.infer<typeof TraceUpsertEventSchema>;
export type TraceQueueEventType = z.infer<typeof TraceQueueEventSchema>;
export type TracesQueueEventType = z.infer<typeof TracesQueueEventSchema>;
export type ProjectQueueEventType = z.infer<typeof ProjectQueueEventSchema>;
export type DatasetRunItemUpsertEventType = z.infer<
typeof DatasetRunItemUpsertEventSchema
>;
export type EvalExecutionEventType = z.infer<typeof EvalExecutionEvent>;
export type LegacyIngestionEventType = z.infer<typeof LegacyIngestionEvent>;
export type IngestionEventQueueType = z.infer<typeof IngestionEvent>;
export const EventBodySchema = z.union([
z.object({
name: z.literal(EventName.TraceUpsert),
payload: z.array(TraceUpsertEventSchema),
}),
z.object({
name: z.literal(EventName.EvaluationExecution),
payload: EvalExecutionEvent,
}),
z.object({
name: z.literal(EventName.BatchExport),
payload: BatchExportJobSchema,
}),
]);
export type EventBodyType = z.infer<typeof EventBodySchema>;
export type ExperimentCreateEventType = z.infer<
typeof ExperimentCreateEventSchema
>;
export type PostHogIntegrationProcessingEventType = z.infer<
typeof PostHogIntegrationProcessingEventSchema
>;
export enum QueueName {
TraceUpsert = "trace-upsert", // Ingestion pipeline adds events on each Trace upsert
TraceDelete = "trace-delete",
ProjectDelete = "project-delete",
EvaluationExecution = "evaluation-execution-queue", // Worker executes Evals
DatasetRunItemUpsert = "dataset-run-item-upsert-queue",
BatchExport = "batch-export-queue",
IngestionQueue = "ingestion-queue", // Process single events with S3-merge
IngestionSecondaryQueue = "secondary-ingestion-queue", // Separates high priority + high throughput projects from other projects.
LegacyIngestionQueue = "legacy-ingestion-queue", // Used for batch processing of Ingestion
CloudUsageMeteringQueue = "cloud-usage-metering-queue",
ExperimentCreate = "experiment-create-queue",
PostHogIntegrationQueue = "posthog-integration-queue",
PostHogIntegrationProcessingQueue = "posthog-integration-processing-queue",
}
export enum QueueJobs {
TraceUpsert = "trace-upsert",
TraceDelete = "trace-delete",
ProjectDelete = "project-delete",
DatasetRunItemUpsert = "dataset-run-item-upsert",
EvaluationExecution = "evaluation-execution-job",
BatchExportJob = "batch-export-job",
EnqueueBatchExportJobs = "enqueue-batch-export-jobs",
LegacyIngestionJob = "legacy-ingestion-job",
CloudUsageMeteringJob = "cloud-usage-metering-job",
IngestionJob = "ingestion-job",
IngestionSecondaryJob = "secondary-ingestion-job",
ExperimentCreateJob = "experiment-create-job",
PostHogIntegrationJob = "posthog-integration-job",
PostHogIntegrationProcessingJob = "posthog-integration-processing-job",
}
export type TQueueJobTypes = {
[QueueName.TraceUpsert]: {
timestamp: Date;
id: string;
payload: TraceUpsertEventType;
payload: TraceQueueEventType;
name: QueueJobs.TraceUpsert;
};
[QueueName.TraceDelete]: {
timestamp: Date;
id: string;
payload: TracesQueueEventType | TraceQueueEventType;
name: QueueJobs.TraceDelete;
};
[QueueName.ProjectDelete]: {
timestamp: Date;
id: string;
payload: ProjectQueueEventType;
name: QueueJobs.ProjectDelete;
};
[QueueName.DatasetRunItemUpsert]: {
timestamp: Date;
id: string;
payload: DatasetRunItemUpsertEventType;
name: QueueJobs.DatasetRunItemUpsert;
};
[QueueName.EvaluationExecution]: {
timestamp: Date;
id: string;
@@ -144,4 +185,22 @@ export type TQueueJobTypes = {
payload: IngestionEventQueueType;
name: QueueJobs.IngestionJob;
};
[QueueName.IngestionSecondaryQueue]: {
timestamp: Date;
id: string;
payload: IngestionEventQueueType;
name: QueueJobs.IngestionJob;
};
[QueueName.ExperimentCreate]: {
timestamp: Date;
id: string;
payload: ExperimentCreateEventType;
name: QueueJobs.ExperimentCreateJob;
};
[QueueName.PostHogIntegrationProcessingQueue]: {
timestamp: Date;
id: string;
payload: PostHogIntegrationProcessingEventType;
name: QueueJobs.PostHogIntegrationProcessingJob;
};
};
@@ -0,0 +1,61 @@
import { Queue } from "bullmq";
import { env } from "../../env";
import { QueueName, QueueJobs } from "../queues";
import { createNewRedisInstance, redisQueueRetryOptions } from "./redis";
import { logger } from "../logger";
export class CloudUsageMeteringQueue {
private static instance: Queue | null = null;
public static getInstance(): Queue | null {
if (!env.STRIPE_SECRET_KEY) {
return null;
}
if (CloudUsageMeteringQueue.instance) {
return CloudUsageMeteringQueue.instance;
}
const newRedis = createNewRedisInstance({
enableOfflineQueue: false,
...redisQueueRetryOptions,
});
CloudUsageMeteringQueue.instance = newRedis
? new Queue(QueueName.CloudUsageMeteringQueue, {
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
CloudUsageMeteringQueue.instance?.on("error", (err) => {
logger.error("CloudUsageMeteringQueue error", err);
});
if (CloudUsageMeteringQueue.instance) {
CloudUsageMeteringQueue.instance.add(
QueueJobs.CloudUsageMeteringJob,
{},
{
repeat: { pattern: "5 * * * *" },
},
);
CloudUsageMeteringQueue.instance.add(
QueueJobs.CloudUsageMeteringJob,
{},
{},
);
}
return CloudUsageMeteringQueue.instance;
}
}
@@ -0,0 +1,47 @@
import { QueueName, TQueueJobTypes } from "../queues";
import { Queue } from "bullmq";
import { createNewRedisInstance, redisQueueRetryOptions } from "./redis";
import { logger } from "../logger";
export class DatasetRunItemUpsertQueue {
private static instance: Queue<
TQueueJobTypes[QueueName.DatasetRunItemUpsert]
> | null = null;
public static getInstance(): Queue<
TQueueJobTypes[QueueName.DatasetRunItemUpsert]
> | null {
if (DatasetRunItemUpsertQueue.instance)
return DatasetRunItemUpsertQueue.instance;
const newRedis = createNewRedisInstance({
enableOfflineQueue: false,
...redisQueueRetryOptions,
});
DatasetRunItemUpsertQueue.instance = newRedis
? new Queue<TQueueJobTypes[QueueName.DatasetRunItemUpsert]>(
QueueName.DatasetRunItemUpsert,
{
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 10_000,
attempts: 5,
delay: 30_000, // 30 seconds
backoff: {
type: "exponential",
delay: 5000,
},
},
},
)
: null;
DatasetRunItemUpsertQueue.instance?.on("error", (err) => {
logger.error("DatasetRunItemUpsertQueue error", err);
});
return DatasetRunItemUpsertQueue.instance;
}
}
@@ -0,0 +1,45 @@
import { Queue } from "bullmq";
import { logger } from "../logger";
import { TQueueJobTypes, QueueName } from "../queues";
import { createNewRedisInstance, redisQueueRetryOptions } from "./redis";
export class EvalExecutionQueue {
private static instance: Queue<
TQueueJobTypes[QueueName.EvaluationExecution]
> | null = null;
public static getInstance(): Queue<
TQueueJobTypes[QueueName.EvaluationExecution]
> | null {
if (EvalExecutionQueue.instance) return EvalExecutionQueue.instance;
const newRedis = createNewRedisInstance({
enableOfflineQueue: false,
...redisQueueRetryOptions,
});
EvalExecutionQueue.instance = newRedis
? new Queue<TQueueJobTypes[QueueName.EvaluationExecution]>(
QueueName.EvaluationExecution,
{
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 10_000,
attempts: 10,
backoff: {
type: "exponential",
delay: 1000,
},
},
},
)
: null;
EvalExecutionQueue.instance?.on("error", (err) => {
logger.error("EvalExecutionQueue error", err);
});
return EvalExecutionQueue.instance;
}
}
@@ -0,0 +1,45 @@
import { Queue } from "bullmq";
import { logger } from "../logger";
import { TQueueJobTypes, QueueName } from "../queues";
import { createNewRedisInstance, redisQueueRetryOptions } from "./redis";
export class ExperimentCreateQueue {
private static instance: Queue<
TQueueJobTypes[QueueName.ExperimentCreate]
> | null = null;
public static getInstance(): Queue<
TQueueJobTypes[QueueName.ExperimentCreate]
> | null {
if (ExperimentCreateQueue.instance) return ExperimentCreateQueue.instance;
const newRedis = createNewRedisInstance({
enableOfflineQueue: false,
...redisQueueRetryOptions,
});
ExperimentCreateQueue.instance = newRedis
? new Queue<TQueueJobTypes[QueueName.ExperimentCreate]>(
QueueName.ExperimentCreate,
{
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 10_000,
attempts: 10,
backoff: {
type: "exponential",
delay: 1000,
},
},
},
)
: null;
ExperimentCreateQueue.instance?.on("error", (err) => {
logger.error("ExperimentCreateQueue error", err);
});
return ExperimentCreateQueue.instance;
}
}
@@ -0,0 +1,48 @@
import { Queue } from "bullmq";
import { QueueName } from "../queues";
import { BatchExportQueue } from "./batchExport";
import { CloudUsageMeteringQueue } from "./cloudUsageMeteringQueue";
import { DatasetRunItemUpsertQueue } from "./datasetRunItemUpsert";
import { EvalExecutionQueue } from "./evalExecutionQueue";
import { ExperimentCreateQueue } from "./experimentCreateQueue";
import { IngestionQueue, SecondaryIngestionQueue } from "./ingestionQueue";
import { LegacyIngestionQueue } from "./legacyIngestion";
import { TraceUpsertQueue } from "./traceUpsert";
import { TraceDeleteQueue } from "./traceDelete";
import { ProjectDeleteQueue } from "./projectDelete";
import { PostHogIntegrationQueue } from "./postHogIntegrationQueue";
import { PostHogIntegrationProcessingQueue } from "./postHogIntegrationProcessingQueue";
export function getQueue(queueName: QueueName): Queue | null {
switch (queueName) {
case QueueName.LegacyIngestionQueue:
return LegacyIngestionQueue.getInstance();
case QueueName.BatchExport:
return BatchExportQueue.getInstance();
case QueueName.CloudUsageMeteringQueue:
return CloudUsageMeteringQueue.getInstance();
case QueueName.DatasetRunItemUpsert:
return DatasetRunItemUpsertQueue.getInstance();
case QueueName.EvaluationExecution:
return EvalExecutionQueue.getInstance();
case QueueName.ExperimentCreate:
return ExperimentCreateQueue.getInstance();
case QueueName.TraceUpsert:
return TraceUpsertQueue.getInstance();
case QueueName.TraceDelete:
return TraceDeleteQueue.getInstance();
case QueueName.IngestionQueue:
return IngestionQueue.getInstance();
case QueueName.ProjectDelete:
return ProjectDeleteQueue.getInstance();
case QueueName.PostHogIntegrationQueue:
return PostHogIntegrationQueue.getInstance();
case QueueName.PostHogIntegrationProcessingQueue:
return PostHogIntegrationProcessingQueue.getInstance();
case QueueName.IngestionSecondaryQueue:
return SecondaryIngestionQueue.getInstance();
default:
const exhaustiveCheckDefault: never = queueName;
throw new Error(`Queue ${queueName} not found`);
}
}
@@ -43,3 +43,45 @@ export class IngestionQueue {
return IngestionQueue.instance;
}
}
export class SecondaryIngestionQueue {
private static instance: Queue<
TQueueJobTypes[QueueName.IngestionSecondaryQueue]
> | null = null;
public static getInstance(): Queue<
TQueueJobTypes[QueueName.IngestionSecondaryQueue]
> | null {
if (SecondaryIngestionQueue.instance)
return SecondaryIngestionQueue.instance;
const newRedis = createNewRedisInstance({
enableOfflineQueue: false,
...redisQueueRetryOptions,
});
SecondaryIngestionQueue.instance = newRedis
? new Queue<TQueueJobTypes[QueueName.IngestionSecondaryQueue]>(
QueueName.IngestionSecondaryQueue,
{
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100_000,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
},
)
: null;
SecondaryIngestionQueue.instance?.on("error", (err) => {
logger.error("SecondaryIngestionQueue error", err);
});
return SecondaryIngestionQueue.instance;
}
}
@@ -0,0 +1,40 @@
import { Queue } from "bullmq";
import { QueueName } from "../queues";
import { createNewRedisInstance, redisQueueRetryOptions } from "./redis";
import { logger } from "../logger";
export class PostHogIntegrationProcessingQueue {
private static instance: Queue | null = null;
public static getInstance(): Queue | null {
if (PostHogIntegrationProcessingQueue.instance) {
return PostHogIntegrationProcessingQueue.instance;
}
const newRedis = createNewRedisInstance({
enableOfflineQueue: false,
...redisQueueRetryOptions,
});
PostHogIntegrationProcessingQueue.instance = newRedis
? new Queue(QueueName.PostHogIntegrationProcessingQueue, {
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100_000,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
PostHogIntegrationProcessingQueue.instance?.on("error", (err) => {
logger.error("PostHogIntegrationProcessingQueue error", err);
});
return PostHogIntegrationProcessingQueue.instance;
}
}
@@ -0,0 +1,55 @@
import { Queue } from "bullmq";
import { QueueName, QueueJobs } from "../queues";
import { createNewRedisInstance, redisQueueRetryOptions } from "./redis";
import { logger } from "../logger";
export class PostHogIntegrationQueue {
private static instance: Queue | null = null;
public static getInstance(): Queue | null {
if (PostHogIntegrationQueue.instance) {
return PostHogIntegrationQueue.instance;
}
const newRedis = createNewRedisInstance({
enableOfflineQueue: false,
...redisQueueRetryOptions,
});
PostHogIntegrationQueue.instance = newRedis
? new Queue(QueueName.PostHogIntegrationQueue, {
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
PostHogIntegrationQueue.instance?.on("error", (err) => {
logger.error("PostHogIntegrationQueue error", err);
});
if (PostHogIntegrationQueue.instance) {
logger.debug("Scheduling jobs for PostHogIntegrationQueue");
PostHogIntegrationQueue.instance
.add(
QueueJobs.PostHogIntegrationJob,
{},
{
repeat: { pattern: "30 * * * *" }, // every hour at 30 minutes past
},
)
.catch((err) => {
logger.error("Error adding PostHogIntegrationJob schedule", err);
});
}
return PostHogIntegrationQueue.instance;
}
}

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