Compare commits

...
77 Commits
Author SHA1 Message Date
Hassieb Pakzad cf42dd726d chore: release v2.49.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-10 16:22:01 +02:00
Hassieb PakzadandGitHub 8aeec63c07 perf: use raw query for trace duration on dataset run table (#2286) 2024-06-10 16:19:31 +02:00
Hassieb Pakzad 0cd3fcfbeb chore: release v2.49.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-10 12:57:14 +02:00
Hassieb PakzadandGitHub a880bdb3b2 perf: boost observationCounts on prompts table (#2284) 2024-06-10 12:48:39 +02:00
Max DeichmannandGitHub 5c6d3e14f2 refactor: add tokenisation to worker (#2281) 2024-06-09 11:48:21 +00:00
Max DeichmannandGitHub 6ccc57312c fix: shared dependencies and improve docker ci (#2273) 2024-06-07 18:45:05 +02:00
Max DeichmannandGitHub c4831d1b25 fix: add lodash to worker (#2270) 2024-06-07 15:45:15 +02:00
Marc Klingen cc08408f0a fix(ui): show title when trying to deleting built-in models 2024-06-07 02:57:18 +02:00
6a41aadc98 fix(ui): handle nested menu states in sidebar (#2230)
* chore: update MainNavigation component to use nested navigation literals

* simplify

---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-06-06 22:42:51 +00:00
9d6efbae19 fix(ui): improve ux across delete buttons (#2258)
* improve ux across delete buttons

* updated delete button on models screen

* trpc errors are logged to console by default

* revert to pointer-events-none on disabled button

---------

Co-authored-by: Mish Ushakov <mishushakov@users.noreply.github.com>
Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-06-07 00:26:04 +02:00
Max DeichmannandGitHub 74be1e87c1 refactor: move ingestion types to shared (#2264)
push
2024-06-06 17:56:35 +00:00
Hassieb Pakzad dd823da1da chore: release v2.49.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-06 17:52:18 +02:00
992f0affbc chore: remove trace fkey constraint from datasetrunitems (#2262)
Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-06-06 17:49:17 +02:00
Mish UshakovandGitHub b8c192324c docs: Updated contributing guide (#2259) 2024-06-06 14:28:57 +02:00
Marc KlingenandGitHub 1416e760ff chore: remove dangling worker/generated/types.ts (#2257) 2024-06-06 14:11:01 +02:00
Marc KlingenandGitHub 4266b46dd9 chore(db): drop legacy pricings table, replaced by models (#2256) 2024-06-06 11:36:08 +02:00
Marc KlingenandGitHub 1933f8f201 feat: add token/character count support for gemini-1.5-flash and gemini-1.5-pro (#2255) 2024-06-06 11:21:42 +02:00
Marc KlingenandGitHub 1e35eb3a66 fix(ui): encode prompt name on link from traces (#2253) 2024-06-06 08:30:52 +00:00
Marc KlingenandGitHub 5478c5e317 feat(ui): use dropdownmenu for version modal (#2252) 2024-06-06 08:20:01 +00:00
Marc KlingenandGitHub 140452c89e feat(ui): add upgrade popover to version number (#2251) 2024-06-06 00:48:23 +00:00
Marc KlingenandGitHub 7ac92e83da feat(ui): use pagination instead of virtualization on sessions view (#2248) 2024-06-05 22:21:01 +00:00
Marc Klingen 8a65bad7d4 chore: release v2.48.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-05 23:04:25 +02:00
Marc Klingen 6c6348bfbe chore(rbac): grant new scoreConfigs:CUD scope to MEMBER role 2024-06-05 23:02:42 +02:00
Marlies Mayerhofer 9ff7fc942d fix: add maintainers heading in contributing file 2024-06-05 22:53:30 +02:00
Marlies Mayerhofer a3f59e7e95 feat: enhance robustness of multi select item search 2024-06-05 22:46:48 +02:00
Max DeichmannandGitHub 445bdcb65c chore: log level worker (#2246)
* push

* push
2024-06-05 20:34:12 +02:00
Max DeichmannandGitHub 8230aa79b3 chore: remove comments (#2245) 2024-06-05 20:11:10 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
57ca7fb478 chore(deps-dev): bump @typescript-eslint/parser from 7.7.0 to 7.12.0 (#2222)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.7.0 to 7.12.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.12.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 20:05:17 +02:00
marliessophieandGitHub 2db88101e1 feat: add score and score config user tracking events (#2240)
* feat: add score and score config user tracking events

* add: icon for annotation button
2024-06-05 13:12:22 +00:00
Hassieb PakzadandGitHub 669167002a fix: parse object type message content (#2239) 2024-06-05 12:48:30 +00:00
marliessophieandGitHub 2f6ade9354 fix(annotation): improve keyboard navigation experience in drawer (#2236) 2024-06-05 13:41:04 +02:00
Marc Klingen 86f44df5dc chore: release v2.48.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-05 00:32:21 +02:00
marliessophieandGitHub 34da790d5e chore(scores): backfill manual scores with config_id (#2233) 2024-06-05 00:25:10 +02:00
c16d86f7e5 feat(scores): add annotation slider and score config settings for categorical annotation (#2157)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-06-05 00:06:05 +02:00
Marc KlingenandGitHub 1b2b8cc43a ci: fix flaky docker test build step by pinning turbo v1 (#2234) 2024-06-04 23:48:00 +02:00
Marc Klingen 3903790086 docs(api): refer to GET /traces endpoint to fetch large sessions with pagination 2024-06-04 20:22:23 +02:00
6da1280c43 perf(user-table): add timestamp filter to user table (#2231)
* feat: add timestamp filter to user table

* users table definition instead of reusing traces

* add filter to total as well to make table state consistent

---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-06-04 19:08:00 +02:00
Marc Klingen 85343bf26c chore: release v2.47.7
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-04 16:56:48 +02:00
Richard KrümmelandGitHub e3b4dc1928 feat(api): add sessionId filter for traces endpoint (#2227) 2024-06-04 14:38:51 +00:00
Marc KlingenandGitHub a4939bacd8 perf: add index on scores.name (#2228) 2024-06-04 15:35:21 +02:00
Hassieb Pakzad 4441942cf5 chore: release v2.47.6
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-04 14:02:23 +02:00
Hassieb PakzadandGitHub 6597b02c09 perf: boost sessions table performance (#2225) 2024-06-04 13:42:09 +02:00
Marc KlingenandGitHub 0df3ad27dc perf: add index on dataset_items.source_trace_id (#2220) 2024-06-04 01:57:19 +02:00
Marc KlingenandGitHub 696d458a27 fix(cloud): handle posthog settings rbac correctly in frontend (#2223) 2024-06-03 23:35:49 +00:00
Marc KlingenandGitHub fd18ab605d feat(api): add count and cost to daily metrics api (#2219) 2024-06-03 22:41:01 +02:00
Marc Klingen d47d14eecd chore: release v2.47.5
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-03 19:56:18 +02:00
Richard KrümmelandGitHub 24a81e1589 fix: update validation schema for prompt tags (#2217) 2024-06-03 18:51:39 +02:00
Max Deichmann ac0c13f3e5 chore: release v2.47.4
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-03 18:07:58 +02:00
Marc KlingenandGitHub a7b90fa32c feat(ui): preview model cost per 1k, 100k, and 1M units (#2216) 2024-06-03 13:45:10 +00:00
Marc Klingen 9eaf2a2e0f docs(api): add tags to prompt api response type 2024-06-03 14:24:48 +02:00
Max DeichmannandGitHub 61e3cc33bf fix: do not log expected eval errors as sentry errors (#2213) 2024-06-03 12:22:41 +00:00
2be198400d feat(api): add prompt tags via API (#2180)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-06-03 12:10:15 +00:00
Max Deichmann 830df682f9 chore: release v2.47.3
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-03 12:19:27 +02:00
Max DeichmannandGitHub 2a420b2e90 chore: reduce sentry sampling (#2212) 2024-06-03 11:06:56 +02:00
Marc Klingen 53127c34ab chore: add posthog ui_host 2024-06-02 21:18:35 +02:00
Marc Klingen 9c57ff4853 chore: reduce cloud telemetry to 4 times a day 2024-06-02 20:18:55 +02:00
Marc Klingen a71176eda4 chore: disable posthog autocapture 2024-06-02 20:12:54 +02:00
Marc KlingenandGitHub f59a85f412 fix(ui): full prompt name on hover when saving as new version from playground (#2208) 2024-06-02 14:31:01 +00:00
Marc Klingen ec59436522 chore: release v2.47.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-06-02 01:37:09 +02:00
Marc KlingenandGitHub 53a3d58a2f fix(ui): do not run llm api key query when it is not available (#2205) 2024-06-02 01:35:41 +02:00
Marc KlingenandGitHub c1162a3a8b chore: update support page to include GH discussions (#2204) 2024-06-01 15:59:34 +00:00
Marc Klingen 43d8c4ec99 chore: release v2.47.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-05-31 16:15:00 +02:00
Marc KlingenandGitHub 3a88315ac5 perf: add date range filter to scores, default to 7 days across core tables (#2198) 2024-05-31 14:12:16 +00:00
Marc KlingenandGitHub e06b96a5ee fix(auth): add custom timeout to sso config query (#2192) 2024-05-31 10:21:03 +00:00
Marc KlingenandGitHub 307f54ef8a chore(cloud): update usage & billing copy (#2193) 2024-05-30 23:38:43 +00:00
Marc KlingenandGitHub 196dc1bf00 feat: support claude-3 pricing on google vertex (#2191) 2024-05-30 23:36:11 +02:00
Marc Klingen ee2c0fb27b chore: release v2.47.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (20) (push) Waiting to run
CI/CD / tests-worker (20) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-05-30 19:26:01 +02:00
Marc KlingenandGitHub 27623de650 fix(ui): do not wrap tags within tables (#2189) 2024-05-30 14:05:39 +00:00
Marc Klingen 61c8f6c269 docs update integrations in readme 2024-05-30 15:40:02 +02:00
Marc KlingenandGitHub cc9e21ccb3 perf(datasets): lateral join on runsByDatasetId, remove unused include on runitemsByRunIdOrItemId (#2183) 2024-05-29 23:59:48 +00:00
078ceb7ff1 feat(ui): add client-side error notifications (#2166)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-05-29 14:41:41 +00:00
Marc KlingenandGitHub 9436c33025 fix(ui): json views should use resolve theme instead of the literal theme which can be system (#2168) 2024-05-29 14:14:38 +00:00
Marc KlingenandGitHub 88b3e1bea6 chore(cloud): remove posthog ingestion metrics as we moved to airbyte (#2179) 2024-05-29 13:22:02 +00:00
Richard KrümmelandGitHub da042d8284 fix(ui): allow for long model names in select filter (#2173) 2024-05-29 13:42:51 +02:00
Max DeichmannandGitHub 50264701f6 chore: reduce logging (#2178)
push
2024-05-29 09:14:33 +00:00
Marc KlingenandGitHub 3ee8a2e026 perf(db): add cursors and indicies on cursors (#2174) 2024-05-29 00:06:40 +02:00
Marc KlingenandGitHub ed04357815 feat(ui): add linter to json inputs (#2171) 2024-05-28 14:40:13 +00:00
179 changed files with 4590 additions and 2230 deletions
+1 -1
View File
@@ -47,5 +47,5 @@ REDIS_PORT=6379
REDIS_AUTH="myredissecret"
LANGFUSE_WORKER_PASSWORD=mybasicauthsecret
# openssl rand -base64 32 used only here
# openssl rand -hex 32 used only here
ENCRYPTION_KEY=6c16874e5c0f0cc74ddec00425fa99fbe9ffbe412b7d5d906a4b00005df91403
+20 -12
View File
@@ -40,19 +40,27 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Build Web Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ./web/Dockerfile
push: false
- name: Build and run both images from compose
run: |
docker compose -f docker-compose.build.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
- name: Build Worker Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ./worker/Dockerfile
push: false
- name: Check server health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
- name: Check worker health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3030/api/health; do sleep 2; done'
- name: Ensure no unhealthy status
run: |
if docker-compose ps | grep "(unhealthy)"; then
echo "One or more services are unhealthy"
exit 1
else
echo "All services are healthy"
fi
tests-web:
runs-on: ubuntu-latest
+13 -1
View File
@@ -191,6 +191,16 @@ Requirements
pnpm run dev
```
7. Open the web app in the browser:
http://localhost:3000
8. Log in as a test user (after you ran `db:seed` command):
Username: demo@langfuse.com
Password: password
## Monorepo quickstart
- Available packages and their dependencies
@@ -358,7 +368,9 @@ The background color of the following component will be `hsl(var(--primary))` an
By following these guidelines, you can ensure that any contributions to our theme are consistent, maintainable, and aligned with our design system.
## Using secrets stored in 1Password
## Maintainers
### Using secrets stored in 1Password
When applying changes to non-local environments, you may need to use secrets stored in 1Password. We use the 1Password CLI for this purpose.
+18 -14
View File
@@ -106,22 +106,26 @@ See the [→ Quickstart](https://langfuse.com/docs/get-started) to integrate Lan
### Integrations
| Integration | Supports | Description |
| ---------------------------------------------------------------- | ------------- | ----------------------------------------------------------- |
| [**SDK** - _recommended_](https://langfuse.com/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI SDK](https://langfuse.com/docs/integrations/openai) | Python, JS/TS | Automated instrumentation of OpenAI SDK. |
| [Langchain](https://langfuse.com/docs/integrations/langchain) | Python, JS/TS | Instrumentation via Langchain callbacks. |
| [LlamaIndex](https://langfuse.com/docs/integrations/llama-index) | Python | Automated instrumentation via LlamaIndex callback system. |
| [API](https://langfuse.com/docs/api) | | Directly call the public API. OpenAPI spec available. |
| Integration | Supports | Description |
| -------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [SDK](/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI](/docs/integrations/openai) | Python, JS/TS | Automated instrumentation using drop-in replacement of OpenAI SDK. |
| [Langchain](/docs/integrations/langchain) | Python, JS/TS | Automated instrumentation by passing callback handler to Langchain application. |
| [LlamaIndex](/docs/integrations/llama-index/get-started) | Python | Automated instrumentation via LlamaIndex callback system. |
| [Haystack](/docs/integrations/haystack) | Python | Automated instrumentation via Haystack content tracing system. |
| [LiteLLM](/docs/integrations/litellm) | Python, JS/TS (proxy only) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
| [API](/docs/api) | | Directly call the public API. OpenAPI spec available. |
External projects/packages that integrate with Langfuse:
Packages that integrate with Langfuse:
| Name | Description |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [LiteLLM](https://langfuse.com/docs/integrations/litellm) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
| [Flowise](https://langfuse.com/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
| [Langflow](https://langfuse.com/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
| [Superagent](https://langfuse.com/docs/integrations/superagent) | Open Source AI Assistant Framework & API for prototyping and deployment of agents. |
| Name | Description |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [Instructor](/docs/integrations/instructor) | Library to get structured LLM outputs (JSON, Pydantic) |
| [Mirascope](/docs/integrations/mirascope) | Python toolkit for building LLM applications. |
| [AI SDK by Vercel](/docs/sdk/typescript/example-vercel-ai) | Typescript SDK that makes streaming LLM outputs super easy. |
| [Flowise](/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
| [Langflow](/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
| [Superagent](/docs/integrations/superagent) | Open Source AI Assistant Framework & API for prototyping and deployment of agents. |
## Questions and feedback
+10
View File
@@ -20,6 +20,11 @@ services:
- LANGFUSE_WORKER_HOST=${LANGFUSE_WORKER_HOST:-worker}
- LANGFUSE_WORKER_PASSWORD=${LANGFUSE_WORKER_PASSWORD:-mybasicauthsecret}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/public/health"]
interval: 30s
timeout: 10s
retries: 3
worker:
build:
@@ -41,6 +46,11 @@ services:
- REDIS_AUTH=${REDIS_AUTH:-myredissecret}
- LANGFUSE_WORKER_PASSWORD=${LANGFUSE_WORKER_PASSWORD:-mybasicauthsecret}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3030/api/health"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7.2.4
+1 -1
View File
@@ -33,7 +33,7 @@
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"@types/node": "^20.11.29",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/parser": "^7.12.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
+24 -2
View File
@@ -6,7 +6,7 @@ import CognitoProvider from "next-auth/providers/cognito";
import Auth0Provider from "next-auth/providers/auth0";
import AzureADProvider from "next-auth/providers/azure-ad";
import { isEeAvailable } from "..";
import { prisma } from "@langfuse/shared/src/db";
import { type SsoConfig, prisma } from "@langfuse/shared/src/db";
import { encrypt, decrypt } from "@langfuse/shared/encryption";
import { SsoProviderSchema } from "./types";
import { type NextApiRequest, type NextApiResponse } from "next";
@@ -26,14 +26,36 @@ let cachedSsoConfigs: {
async function getSsoConfigs(): Promise<SsoProviderSchema[]> {
if (!isEeAvailable) return [];
const CACHE_TTL = 60 * 1000; // 1 minute
const DB_MAX_WAIT = 2000; // 2 seconds
const DB_TIMEOUT = 3000; // 3 seconds
// Set/refresh the cache if it's empty or expired
if (
cachedSsoConfigs === null ||
Date.now() - cachedSsoConfigs.timestamp > CACHE_TTL
) {
// findMany with custom timeout via $transaction
let dbConfigs: SsoConfig[] = [];
try {
dbConfigs = await prisma.$transaction(
async (prisma) => prisma.ssoConfig.findMany(),
{
maxWait: DB_MAX_WAIT,
timeout: DB_TIMEOUT,
}
);
} catch (e) {
// cache empty array to prevent repeated DB calls on error
cachedSsoConfigs = {
data: [],
timestamp: Date.now(),
};
// caught and logged in the caller
throw e;
}
// transform into zod object
const dbConfigs = await prisma.ssoConfig.findMany();
const parsedSsoConfigs = dbConfigs
.map((v) => {
try {
+17 -4
View File
@@ -48,12 +48,25 @@ types:
date: date
countTraces: integer
countObservations: integer
totalCost: double
totalCost:
type: double
docs: Total model cost in USD
usage: list<UsageByModel>
UsageByModel:
docs: Daily usage of a given model. Usage corresponds to the unit set for the specific model (e.g. tokens).
properties:
model: optional<string>
inputUsage: integer
outputUsage: integer
totalUsage: integer
inputUsage:
type: integer
docs: Total number of generation input units (e.g. tokens)
outputUsage:
type: integer
docs: Total number of generation output units (e.g. tokens)
totalUsage:
type: integer
docs: Total number of generation total units (e.g. tokens)
countTraces: integer
countObservations: integer
totalCost:
type: double
docs: Total model cost in USD
+18 -3
View File
@@ -70,14 +70,24 @@ types:
name: string
prompt: list<ChatMessage>
config: optional<unknown>
labels: optional<list<string>>
labels:
type: optional<list<string>>
docs: List of deployment labels of this prompt version.
tags:
type: optional<list<string>>
docs: List of tags to apply to all versions of this prompt.
CreateTextPromptRequest:
properties:
name: string
prompt: string
config: optional<unknown>
labels: optional<list<string>>
labels:
type: optional<list<string>>
docs: List of deployment labels of this prompt version.
tags:
type: optional<list<string>>
docs: List of tags to apply to all versions of this prompt.
Prompt:
union:
@@ -89,7 +99,12 @@ types:
name: string
version: integer
config: unknown
labels: list<string>
labels:
type: list<string>
docs: List of deployment labels of this prompt version.
tags:
type: list<string>
docs: List of tags. Used to filter via UI and API. The same across versions of a prompt.
ChatMessage:
properties:
+1 -1
View File
@@ -7,7 +7,7 @@ service:
base-path: /api/public
endpoints:
get:
docs: Get a session
docs: Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=<sessionId>`
method: GET
path: /sessions/{sessionId}
path-parameters:
+1
View File
@@ -30,6 +30,7 @@ service:
docs: Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit.
userId: optional<string>
name: optional<string>
sessionId: optional<string>
fromTimestamp:
type: optional<datetime>
docs: Retrieve only traces newer than this datetime (ISO 8601).
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "langfuse",
"version": "2.46.0",
"version": "2.49.2",
"author": "engineering@langfuse.com",
"license": "MIT",
"private": true,
+1 -1
View File
@@ -9,7 +9,7 @@
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/parser": "^7.12.0",
"@vercel/style-guide": "^6.0.0",
"eslint-config-next": "^14.2.3",
"eslint-config-prettier": "^9.1.0",
+2 -1
View File
@@ -52,6 +52,7 @@
"bcryptjs": "^2.4.3",
"kysely": "^0.27.3",
"langchain": "^0.1.36",
"lodash": "^4.17.21",
"prisma-extension-kysely": "^2.1.0",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.5"
@@ -63,7 +64,7 @@
"@types/node": "^20.11.29",
"@types/pg": "^8.11.6",
"@types/uuid": "^9.0.8",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/parser": "^7.12.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
+5 -21
View File
@@ -32,20 +32,10 @@ export const ScoreSource = {
export type ScoreSource = (typeof ScoreSource)[keyof typeof ScoreSource];
export const ScoreDataType = {
CATEGORICAL: "CATEGORICAL",
NUMERIC: "NUMERIC"
NUMERIC: "NUMERIC",
BOOLEAN: "BOOLEAN"
} as const;
export type ScoreDataType = (typeof ScoreDataType)[keyof typeof ScoreDataType];
export const PricingUnit = {
PER_1000_TOKENS: "PER_1000_TOKENS",
PER_1000_CHARS: "PER_1000_CHARS"
} as const;
export type PricingUnit = (typeof PricingUnit)[keyof typeof PricingUnit];
export const TokenType = {
PROMPT: "PROMPT",
COMPLETION: "COMPLETION",
TOTAL: "TOTAL"
} as const;
export type TokenType = (typeof TokenType)[keyof typeof TokenType];
export const DatasetStatus = {
ACTIVE: "ACTIVE",
ARCHIVED: "ARCHIVED"
@@ -251,6 +241,7 @@ export type Observation = {
status_message: string | null;
version: string | null;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
model: string | null;
internal_model: string | null;
modelParameters: unknown | null;
@@ -308,14 +299,6 @@ export type PosthogIntegration = {
enabled: boolean;
created_at: Generated<Timestamp>;
};
export type Pricing = {
id: string;
model_name: string;
pricing_unit: Generated<PricingUnit>;
price: string;
currency: Generated<string>;
token_type: TokenType;
};
export type Project = {
id: string;
created_at: Generated<Timestamp>;
@@ -358,6 +341,8 @@ export type Score = {
observation_id: string | null;
config_id: string | null;
string_value: string | null;
created_at: Generated<Timestamp>;
updated_at: Generated<Timestamp>;
data_type: Generated<ScoreDataType>;
};
export type ScoreConfig = {
@@ -467,7 +452,6 @@ export type DB = {
observations: Observation;
observations_view: ObservationView;
posthog_integrations: PosthogIntegration;
pricings: Pricing;
project_memberships: ProjectMembership;
projects: Project;
prompts: Prompt;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "observations" ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "scores" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "audit_logs_updated_at_idx" ON "audit_logs"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_items_created_at_idx" ON "dataset_items"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_items_updated_at_idx" ON "dataset_items"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_run_items_created_at_idx" ON "dataset_run_items"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_run_items_updated_at_idx" ON "dataset_run_items"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_runs_created_at_idx" ON "dataset_runs"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_runs_updated_at_idx" ON "dataset_runs"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "datasets_created_at_idx" ON "datasets"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "datasets_updated_at_idx" ON "datasets"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "job_executions_updated_at_idx" ON "job_executions"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "observations_updated_at_idx" ON "observations"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "prompts_created_at_idx" ON "prompts"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "prompts_updated_at_idx" ON "prompts"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "score_configs_created_at_idx" ON "score_configs"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "score_configs_updated_at_idx" ON "score_configs"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "scores_created_at_idx" ON "scores"("created_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "scores_updated_at_idx" ON "scores"("updated_at");
@@ -0,0 +1,3 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "trace_sessions_updated_at_idx" ON "trace_sessions"("updated_at");
@@ -0,0 +1,7 @@
-- Google Vertex uses @ to separate model name and version
UPDATE "public"."models" SET "match_pattern" = '(?i)^(claude-3-haiku(-|@)?20240307)$' WHERE "id" = 'cltr0w45b000008k1407o9qv1';
UPDATE "public"."models" SET "match_pattern" = '(?i)^(claude-3-opus(-|@)?20240229)$' WHERE "id" = 'cltgy0iuw000008le3vod1hhy';
UPDATE "public"."models" SET "match_pattern" = '(?i)^(claude-3-sonnet(-|@)?20240229)$' WHERE "id" = 'cltgy0pp6000108le56se7bl3';
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX CONCURRENTLY "dataset_items_source_trace_id_idx" ON "dataset_items" USING HASH ("source_trace_id");
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "scores_project_id_name_idx" ON "scores"("project_id", "name");
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "ScoreDataType" ADD VALUE 'BOOLEAN';
@@ -0,0 +1,50 @@
-- Create manual-score config for any project with manual scores and link config_id to scores
BEGIN;
WITH project_configs AS (
INSERT INTO score_configs (id,
project_id,
name,
data_type,
is_archived,
min_value,
max_value,
description)
SELECT
gen_random_uuid () AS id,
s.project_id,
'manual-score',
'NUMERIC',
FALSE,
- 1,
1,
'Langfuse legacy annotation score.'
FROM ( SELECT DISTINCT
project_id
FROM
scores
WHERE
name = 'manual-score'
AND source = 'ANNOTATION') s
WHERE
NOT EXISTS (
SELECT
1
FROM
score_configs sc
WHERE
sc.name = 'manual-score'
AND sc.project_id = s.project_id)
RETURNING
id,
project_id
)
UPDATE
scores
SET
config_id = pc.id
FROM
project_configs pc
WHERE
scores.project_id = pc.project_id
AND scores.name = 'manual-score';
COMMIT;
@@ -0,0 +1,5 @@
-- does not include pricing yet, will be added as soon as it is calculated at ingestion time
INSERT INTO "public"."models" ("id", "model_name", "match_pattern", "unit") VALUES ('clx30djsn0000w9mzebiv41we', 'gemini-1.5-flash', '(?i)^(gemini-1.5-flash)(@[a-zA-Z0-9]+)?$', 'CHARACTERS');
INSERT INTO "public"."models" ("id", "model_name", "match_pattern", "unit") VALUES ('clx30hkrx0000w9mz7lqi0ial', 'gemini-1.5-pro', '(?i)^(gemini-1.5-pro)(@[a-zA-Z0-9]+)?$', 'CHARACTERS');
@@ -0,0 +1,14 @@
/*
Warnings:
- You are about to drop the `pricings` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropTable
DROP TABLE "pricings";
-- DropEnum
DROP TYPE "PricingUnit";
-- DropEnum
DROP TYPE "TokenType";
@@ -0,0 +1,3 @@
-- DropForeignKey
ALTER TABLE "dataset_run_items" DROP CONSTRAINT "dataset_run_items_trace_id_fkey";
ALTER TABLE "dataset_run_items" DROP CONSTRAINT "dataset_run_items_observation_id_fkey";
+44 -47
View File
@@ -206,6 +206,7 @@ model TraceSession {
@@id([id, projectId])
@@index([projectId])
@@index([createdAt])
@@index([updatedAt])
@@map("trace_sessions")
}
@@ -232,9 +233,8 @@ model Trace {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
DatasetRunItems DatasetRunItems[]
DatasetItem DatasetItem[]
JobExecution JobExecution[]
DatasetItem DatasetItem[]
JobExecution JobExecution[]
@@index([projectId])
@@index([sessionId])
@@ -293,24 +293,24 @@ model Observation {
statusMessage String? @map("status_message")
version String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
// GENERATION ONLY
model String?
internalModel String? @map("internal_model")
internalModel String? @map("internal_model")
modelParameters Json?
input Json?
output Json?
promptTokens Int @default(0) @map("prompt_tokens")
completionTokens Int @default(0) @map("completion_tokens")
totalTokens Int @default(0) @map("total_tokens")
promptTokens Int @default(0) @map("prompt_tokens")
completionTokens Int @default(0) @map("completion_tokens")
totalTokens Int @default(0) @map("total_tokens")
unit String?
inputCost Decimal? @map("input_cost")
outputCost Decimal? @map("output_cost")
totalCost Decimal? @map("total_cost")
completionStartTime DateTime? @map("completion_start_time")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
inputCost Decimal? @map("input_cost")
outputCost Decimal? @map("output_cost")
totalCost Decimal? @map("total_cost")
completionStartTime DateTime? @map("completion_start_time")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
derivedDatasetItems DatasetItem[]
datasetRunItems DatasetRunItems[]
promptId String? @map("prompt_id")
prompt Prompt? @relation(fields: [promptId], onDelete: SetNull, references: [id])
@@ -324,6 +324,7 @@ model Observation {
@@index([type])
@@index(startTime)
@@index(createdAt)
@@index(updatedAt)
@@index(projectId)
@@index(parentObservationId)
@@index(model)
@@ -406,6 +407,8 @@ model Score {
observationId String? @map("observation_id")
configId String? @map("config_id")
stringValue String? @map("string_value")
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)
@@ -414,11 +417,14 @@ model Score {
@@index(timestamp)
@@index([value])
@@index([projectId])
@@index([projectId, name])
@@index([authorUserId])
@@index([configId])
@@index([traceId], type: Hash)
@@index([observationId], type: Hash)
@@index([source])
@@index([createdAt])
@@index([updatedAt])
@@map("scores")
}
@@ -448,35 +454,15 @@ model ScoreConfig {
@@index([isArchived])
@@index([projectId])
@@index([categories])
@@index([createdAt])
@@index([updatedAt])
@@map("score_configs")
}
enum ScoreDataType {
CATEGORICAL
NUMERIC
}
enum PricingUnit {
PER_1000_TOKENS
PER_1000_CHARS
}
enum TokenType {
PROMPT
COMPLETION
TOTAL
}
model Pricing {
id String @id @default(cuid())
modelName String @map("model_name")
pricingUnit PricingUnit @default(PER_1000_TOKENS) @map("pricing_unit")
price Decimal
currency String @default("USD")
tokenType TokenType @map("token_type")
@@index(modelName)
@@map("pricings")
BOOLEAN
}
model CronJobs {
@@ -502,6 +488,8 @@ model Dataset {
@@unique([projectId, name])
@@index([projectId], type: Hash)
@@index([createdAt])
@@index([updatedAt])
@@map("datasets")
}
@@ -521,8 +509,11 @@ model DatasetItem {
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
datasetRunItems DatasetRunItems[]
@@index([sourceTraceId], type: Hash)
@@index([sourceObservationId], type: Hash)
@@index([datasetId], type: Hash)
@@index([createdAt])
@@index([updatedAt])
@@map("dataset_items")
}
@@ -544,26 +535,28 @@ model DatasetRuns {
@@unique([datasetId, name])
@@index([datasetId], type: Hash)
@@index([createdAt])
@@index([updatedAt])
@@map("dataset_runs")
}
model DatasetRunItems {
id String @id @default(cuid())
datasetRunId String @map("dataset_run_id")
datasetRun DatasetRuns @relation(fields: [datasetRunId], references: [id], onDelete: Cascade)
datasetItemId String @map("dataset_item_id")
datasetItem DatasetItem @relation(fields: [datasetItemId], references: [id], onDelete: Cascade)
traceId String @map("trace_id")
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
observationId String? @map("observation_id")
observation Observation? @relation(fields: [observationId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
id String @id @default(cuid())
datasetRunId String @map("dataset_run_id")
datasetRun DatasetRuns @relation(fields: [datasetRunId], references: [id], onDelete: Cascade)
datasetItemId String @map("dataset_item_id")
datasetItem DatasetItem @relation(fields: [datasetItemId], references: [id], onDelete: Cascade)
traceId String @map("trace_id")
observationId String? @map("observation_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
@@index([datasetRunId], type: Hash)
@@index([datasetItemId], type: Hash)
@@index([observationId], type: Hash)
@@index([traceId])
@@index([createdAt])
@@index([updatedAt])
@@map("dataset_run_items")
}
@@ -606,6 +599,8 @@ model Prompt {
@@index([projectId, name, version])
@@index([projectId, id])
@@index([projectId])
@@index([createdAt])
@@index([updatedAt])
@@index([tags(ops: ArrayOps)], type: Gin)
@@map("prompts")
}
@@ -653,6 +648,7 @@ model AuditLog {
@@index([projectId])
@@index([createdAt])
@@index([updatedAt])
@@map("audit_logs")
}
@@ -750,6 +746,7 @@ model JobExecution {
@@index([jobOutputScoreId])
@@index([jobInputTraceId])
@@index([createdAt])
@@index([updatedAt])
@@map("job_executions")
}
+103 -3
View File
@@ -4,6 +4,7 @@ import {
type Prisma,
ObservationType,
ScoreSource,
ScoreDataType,
} from "../src/index";
import { hash } from "bcryptjs";
import { parseArgs } from "node:util";
@@ -39,6 +40,7 @@ async function main() {
name: "Demo User",
email: "demo@langfuse.com",
password: await hash("password", 12),
image: "https://static.langfuse.com/langfuse-dev%2Fexample-avatar.png",
},
});
@@ -144,6 +146,11 @@ async function main() {
});
}
const configIdsAndNames = await generateConfigsForProject([
project1,
project2,
]);
const promptIds = await generatePromptsForProject([project1, project2]);
const envTags = [null, "development", "staging", "production"];
@@ -157,7 +164,8 @@ async function main() {
colorTags,
project1,
project2,
promptIds
promptIds,
configIdsAndNames
);
console.log(
@@ -453,13 +461,15 @@ function createObjects(
colorTags: (string | null)[],
project1: Project,
project2: Project,
promptIds: Map<string, string[]>
promptIds: Map<string, string[]>,
configIdsAndNames: Map<string, { name: string; id: string }[]>
) {
const traces: Prisma.TraceCreateManyInput[] = [];
const observations: Prisma.ObservationCreateManyInput[] = [];
const scores: Prisma.ScoreCreateManyInput[] = [];
const sessions: Prisma.TraceSessionCreateManyInput[] = [];
const events: Prisma.ObservationCreateManyInput[] = [];
const configs: Prisma.ScoreConfigCreateManyInput[] = [];
for (let i = 0; i < traceVolume; i++) {
// print progress to console with a progress bar that refreshes every 10 iterations
@@ -511,17 +521,28 @@ function createObjects(
traces.push(trace);
const configArray = configIdsAndNames.get(projectId) ?? [];
const randomIndex = Math.floor(Math.random() * 3);
const config =
configArray.length >= randomIndex - 1 && configArray[randomIndex];
const { name: annotationScoreName, id: configId } = config || {
name: "manual-score",
id: undefined,
};
const traceScores = [
...(Math.random() > 0.5
? [
{
traceId: trace.id,
name: "manual-score",
name: annotationScoreName,
value: Math.floor(Math.random() * 3) - 1,
timestamp: traceTs,
source: ScoreSource.ANNOTATION,
projectId,
authorUserId: `user-${i}`,
dataType: ScoreDataType.NUMERIC,
...(configId ? { configId } : {}),
},
]
: []),
@@ -534,6 +555,7 @@ function createObjects(
timestamp: traceTs,
source: ScoreSource.API,
projectId,
dataType: ScoreDataType.NUMERIC,
},
]
: []),
@@ -772,6 +794,7 @@ function createObjects(
traces,
observations,
scores,
configs,
sessions: uniqueSessions,
events,
};
@@ -954,3 +977,80 @@ async function generatePrompts(project: Project) {
}
return promptIds;
}
async function generateConfigsForProject(projects: Project[]) {
const projectIdsToConfigs: Map<string, { name: string; id: string }[]> =
new Map();
await Promise.all(
projects.map(async (project) => {
const configNameAndId = await generateConfigs(project);
projectIdsToConfigs.set(project.id, configNameAndId);
})
);
return projectIdsToConfigs;
}
async function generateConfigs(project: Project) {
const configNameAndId: { name: string; id: string }[] = [];
const configs = [
{
id: `config-${v4()}`,
name: "manual-score",
dataType: ScoreDataType.NUMERIC,
projectId: project.id,
isArchived: false,
},
{
id: `config-${v4()}`,
projectId: project.id,
name: "Accuracy",
dataType: ScoreDataType.CATEGORICAL,
categories: [
{ label: "Incorrect", value: 0 },
{ label: "Partially Correct", value: 1 },
{ label: "Correct", value: 2 },
],
isArchived: false,
},
{
id: `config-${v4()}`,
projectId: project.id,
name: "Toxicity",
dataType: ScoreDataType.BOOLEAN,
categories: [
{ label: "True", value: 1 },
{ label: "False", value: 0 },
],
description:
"Used to indicate if text was harmful or offensive in nature.",
isArchived: false,
},
];
for (const config of configs) {
await prisma.scoreConfig.upsert({
where: {
id_projectId: {
projectId: config.projectId,
id: config.id,
},
},
create: {
id: config.id,
projectId: config.projectId,
name: config.name,
dataType: config.dataType,
categories: config.categories,
isArchived: config.isArchived,
},
update: {
id: config.id,
},
});
configNameAndId.push({ name: config.name, id: config.id });
}
return configNameAndId;
}
@@ -1,9 +1,9 @@
import lodash from "lodash";
import { z } from "zod";
import { ModelUsageUnit } from "@langfuse/shared";
import { NonEmptyString, jsonSchema } from "@/src/utils/zod";
import { ObservationLevel } from "@langfuse/shared";
import { NonEmptyString, jsonSchema } from "../../utils/zod";
import { ModelUsageUnit } from "../../constants";
import { ObservationLevel } from "@prisma/client";
export const Usage = z.object({
input: z.number().int().nullish(),
@@ -109,7 +109,7 @@ export const CreateGenerationBody = CreateSpanBody.extend({
z.string(),
z
.union([z.string(), z.number(), z.boolean(), z.array(z.string())])
.nullish(),
.nullish()
)
.nullish(),
usage: usage,
@@ -131,7 +131,7 @@ export const UpdateGenerationBody = UpdateSpanBody.extend({
z.string(),
z
.union([z.string(), z.number(), z.boolean(), z.array(z.string())])
.nullish(),
.nullish()
)
.nullish(),
usage: usage,
@@ -195,7 +195,7 @@ export const LegacyGenerationsCreateSchema = z.object({
modelParameters: z
.record(
z.string(),
z.union([z.string(), z.number(), z.boolean()]).nullish(),
z.union([z.string(), z.number(), z.boolean()]).nullish()
)
.nullish(),
prompt: jsonSchema.nullish(),
@@ -219,7 +219,7 @@ export const LegacyGenerationPatchSchema = z.object({
modelParameters: z
.record(
z.string(),
z.union([z.string(), z.number(), z.boolean()]).nullish(),
z.union([z.string(), z.number(), z.boolean()]).nullish()
)
.nullish(),
prompt: jsonSchema.nullish(),
@@ -243,7 +243,7 @@ export const LegacyObservationBody = z.object({
modelParameters: z
.record(
z.string(),
z.union([z.string(), z.number(), z.boolean()]).nullish(),
z.union([z.string(), z.number(), z.boolean()]).nullish()
)
.nullish(),
input: jsonSchema.nullish(),
+4 -4
View File
@@ -1,7 +1,7 @@
import { Prisma } from "@prisma/client";
import {
ColumnDefinition,
type TableNames as TableName,
type TableNames,
} from "./interfaces/tableDefinition";
import { FilterState } from "./types";
import { filterOperators } from "./interfaces/filters";
@@ -27,7 +27,7 @@ const arrayOperatorReplacements = {
export function tableColumnsToSqlFilterAndPrefix(
filters: FilterState,
tableColumns: ColumnDefinition[],
table: TableName
table: TableNames
): Prisma.Sql {
const sql = tableColumnsToSqlFilter(filters, tableColumns, table);
if (sql === Prisma.empty) {
@@ -43,7 +43,7 @@ export function tableColumnsToSqlFilterAndPrefix(
export function tableColumnsToSqlFilter(
filters: FilterState,
tableColumns: ColumnDefinition[],
table: TableName
table: TableNames
): Prisma.Sql {
const internalFilters = filters.map((filter) => {
// Get column definition to map column to internal name, e.g. "t.id"
@@ -153,7 +153,7 @@ export function tableColumnsToSqlFilter(
const castValueToPostgresTypes = (
column: ColumnDefinition,
table: TableName
table: TableNames
) => {
return column.name === "type" &&
(table === "observations" ||
+3
View File
@@ -9,6 +9,9 @@ export * from "./filterToPrisma";
export * from "./tracesTable";
export * from "./server/auth";
export * from "./observationsTable";
export * from "./features/ingestion/types";
export * from "./utils/zod";
export * from "./utils/json";
export { env } from "./env";
// llm api
@@ -42,6 +42,7 @@ export const tableNames = [
"traces_parent_observation_scores",
"sessions",
"prompts",
"users",
] as const;
export type TableNames = (typeof tableNames)[number];
+2 -1
View File
@@ -30,4 +30,5 @@ export type TableName =
| "sessions"
| "scores"
| "prompts"
| "dashboard";
| "dashboard"
| "users";
@@ -1,6 +1,6 @@
import { type jsonSchema } from "@/src/utils/zod";
import { type z } from "zod";
import lodash from "lodash";
import { jsonSchema } from "./zod";
export const parseJson = (input: string) => {
try {
@@ -37,7 +37,7 @@ export function deepParseJson(json: unknown): unknown {
// Ensure we only iterate over the object's own properties
if (Object.prototype.hasOwnProperty.call(json, key)) {
(json as Record<string, unknown>)[key] = deepParseJson(
(json as Record<string, unknown>)[key],
(json as Record<string, unknown>)[key]
);
}
}
@@ -50,7 +50,7 @@ export function deepParseJson(json: unknown): unknown {
export const mergeJson = (
json1?: z.infer<typeof jsonSchema>,
json2?: z.infer<typeof jsonSchema>,
json2?: z.infer<typeof jsonSchema>
) => {
if (json1 === undefined) {
return json2;
@@ -26,7 +26,7 @@ const jsonSchemaNullable: z.ZodType<JsonNested> = z.lazy(() =>
nestedLiteralSchema,
z.array(jsonSchemaNullable),
z.record(jsonSchemaNullable),
]),
])
);
// Root schema that does not allow nulls at the root level
@@ -35,17 +35,17 @@ export const jsonSchema: z.ZodType<Json> = z.lazy(() =>
rootLiteralSchema,
z.array(jsonSchemaNullable),
z.record(jsonSchemaNullable),
]),
])
);
export const paginationZod = {
page: z.preprocess(
(x) => (x === "" ? undefined : x),
z.coerce.number().default(1),
z.coerce.number().default(1)
),
limit: z.preprocess(
(x) => (x === "" ? undefined : x),
z.coerce.number().lte(100).default(50),
z.coerce.number().lte(100).default(50)
),
};
+214 -411
View File
@@ -49,8 +49,8 @@ importers:
specifier: ^20.11.29
version: 20.11.29
'@typescript-eslint/parser':
specifier: ^7.7.0
version: 7.7.0(eslint@8.57.0)(typescript@5.4.5)
specifier: ^7.12.0
version: 7.12.0(eslint@8.57.0)(typescript@5.4.5)
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -80,10 +80,10 @@ importers:
devDependencies:
'@typescript-eslint/eslint-plugin':
specifier: ^7.1.0
version: 7.3.1(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5)
version: 7.3.1(@typescript-eslint/parser@7.12.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser':
specifier: ^7.7.0
version: 7.7.0(eslint@8.57.0)(typescript@5.4.5)
specifier: ^7.12.0
version: 7.12.0(eslint@8.57.0)(typescript@5.4.5)
'@vercel/style-guide':
specifier: ^6.0.0
version: 6.0.0(eslint@8.57.0)(prettier@3.2.5)(typescript@5.4.5)
@@ -130,7 +130,10 @@ importers:
version: 0.27.3
langchain:
specifier: ^0.1.36
version: 0.1.36
version: 0.1.36(@aws-sdk/client-s3@3.550.0)(@aws-sdk/credential-provider-node@3.549.0)(lodash@4.17.21)
lodash:
specifier: ^4.17.21
version: 4.17.21
prisma-extension-kysely:
specifier: ^2.1.0
version: 2.1.0(@prisma/client@5.13.0)
@@ -160,8 +163,8 @@ importers:
specifier: ^9.0.8
version: 9.0.8
'@typescript-eslint/parser':
specifier: ^7.7.0
version: 7.7.0(eslint@8.57.0)(typescript@5.4.5)
specifier: ^7.12.0
version: 7.12.0(eslint@8.57.0)(typescript@5.4.5)
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -222,6 +225,9 @@ importers:
'@codemirror/lang-json':
specifier: ^6.0.1
version: 6.0.1
'@codemirror/lint':
specifier: ^6.8.0
version: 6.8.0
'@headlessui/react':
specifier: 1.7.18
version: 1.7.18(react-dom@18.2.0)(react@18.2.0)
@@ -312,6 +318,9 @@ importers:
'@radix-ui/react-toggle':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-toggle-group':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
@@ -354,9 +363,6 @@ importers:
'@tanstack/react-table':
specifier: ^8.11.8
version: 8.13.2(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-virtual':
specifier: ^3.5.0
version: 3.5.0(react-dom@18.2.0)(react@18.2.0)
'@tremor/react':
specifier: 3.16.2
version: 3.16.2(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.3)
@@ -380,7 +386,7 @@ importers:
version: 4.22.1(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)
'@uiw/react-codemirror':
specifier: ^4.21.25
version: 4.21.25(@babel/runtime@7.24.5)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.7.1)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0)
version: 4.21.25(@babel/runtime@7.24.5)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0)
ai:
specifier: ^3.0.23
version: 3.0.23(react@18.2.0)(solid-js@1.8.17)(svelte@4.2.17)(vue@3.4.27)(zod@3.22.4)
@@ -492,6 +498,9 @@ importers:
uuid:
specifier: ^9.0.1
version: 9.0.1
vaul:
specifier: ^0.9.1
version: 0.9.1(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
zod:
specifier: ^3.22.4
version: 3.22.4
@@ -553,10 +562,10 @@ importers:
version: 9.0.8
'@typescript-eslint/eslint-plugin':
specifier: ^6.21.0
version: 6.21.0(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5)
version: 6.21.0(@typescript-eslint/parser@7.12.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser':
specifier: ^7.7.0
version: 7.7.0(eslint@8.57.0)(typescript@5.4.5)
specifier: ^7.12.0
version: 7.12.0(eslint@8.57.0)(typescript@5.4.5)
autoprefixer:
specifier: ^10.4.19
version: 10.4.19(postcss@8.4.38)
@@ -605,6 +614,9 @@ importers:
worker:
dependencies:
'@anthropic-ai/tokenizer':
specifier: ^0.0.4
version: 0.0.4
'@langfuse/shared':
specifier: workspace:*
version: link:../packages/shared
@@ -644,6 +656,9 @@ importers:
ioredis:
specifier: ^5.4.1
version: 5.4.1
js-tiktoken:
specifier: ^1.0.12
version: 1.0.12
kysely:
specifier: ^0.27.3
version: 0.27.3
@@ -662,6 +677,9 @@ importers:
pino-pretty:
specifier: ^10.3.1
version: 10.3.1
tiktoken:
specifier: ^1.0.15
version: 1.0.15
uuid:
specifier: ^9.0.1
version: 9.0.1
@@ -694,8 +712,8 @@ importers:
specifier: ^8.11.6
version: 8.11.6
'@typescript-eslint/parser':
specifier: ^7.7.0
version: 7.7.0(eslint@8.57.0)(typescript@5.4.5)
specifier: ^7.12.0
version: 7.12.0(eslint@8.57.0)(typescript@5.4.5)
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -821,7 +839,7 @@ packages:
resolution: {integrity: sha512-EHRKbxlxlc8W4KCBEseByJ7YwyYCmgu9OyN59H9+IYIGPoKv8tXyQXinkeGDI+cI8Tiuz9wk2jZb/kK7AyvL7g==}
dependencies:
'@types/node': 18.19.25
tiktoken: 1.0.13
tiktoken: 1.0.15
dev: false
/@aws-crypto/crc32@3.0.0:
@@ -2076,8 +2094,8 @@ packages:
style-mod: 4.1.2
dev: false
/@codemirror/lint@6.7.1:
resolution: {integrity: sha512-rELba6QJD20/bNXWP/cKTGLrwVEcpa2ViwULCV03ONcY1Je85++7sczVRUlnE4TJMjatx3IJTz6HX4NXi+moXw==}
/@codemirror/lint@6.8.0:
resolution: {integrity: sha512-lsFofvaw0lnPRJlQylNsC4IRt/1lI4OD/yYslrSGVndOJfStc58v+8p9dgGiD90ktOfL7OhBWns1ZETYgz0EJA==}
dependencies:
'@codemirror/state': 6.4.1
'@codemirror/view': 6.26.3
@@ -3335,7 +3353,7 @@ packages:
ansi-styles: 5.2.0
camelcase: 6.3.0
decamelize: 1.2.0
js-tiktoken: 1.0.11
js-tiktoken: 1.0.12
langsmith: 0.1.14
ml-distance: 4.0.1
mustache: 4.2.0
@@ -4245,7 +4263,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.5
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.79)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.79)(react@18.2.0)
@@ -4830,6 +4848,33 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.24.5
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-context': 1.0.1(@types/react@18.2.79)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.79)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-toggle': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.79)(react@18.2.0)
'@types/react': 18.2.79
'@types/react-dom': 18.2.25
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-toggle@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==}
peerDependencies:
@@ -6699,7 +6744,7 @@ packages:
dev: true
optional: true
/@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5):
/@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@7.12.0)(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -6711,7 +6756,7 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5)
@@ -6728,7 +6773,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5):
/@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.12.0)(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -6740,7 +6785,7 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/scope-manager': 7.3.1
'@typescript-eslint/type-utils': 7.3.1(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.5)
@@ -6757,6 +6802,27 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 7.12.0
'@typescript-eslint/types': 7.12.0
'@typescript-eslint/typescript-estree': 7.12.0(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.12.0
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -6778,27 +6844,6 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 7.7.0
'@typescript-eslint/types': 7.7.0
'@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/scope-manager@5.62.0:
resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -6815,6 +6860,14 @@ packages:
'@typescript-eslint/visitor-keys': 6.21.0
dev: true
/@typescript-eslint/scope-manager@7.12.0:
resolution: {integrity: sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==}
engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
'@typescript-eslint/types': 7.12.0
'@typescript-eslint/visitor-keys': 7.12.0
dev: true
/@typescript-eslint/scope-manager@7.2.0:
resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -6831,14 +6884,6 @@ packages:
'@typescript-eslint/visitor-keys': 7.3.1
dev: true
/@typescript-eslint/scope-manager@7.7.0:
resolution: {integrity: sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==}
engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
'@typescript-eslint/types': 7.7.0
'@typescript-eslint/visitor-keys': 7.7.0
dev: true
/@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -6889,6 +6934,11 @@ packages:
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
/@typescript-eslint/types@7.12.0:
resolution: {integrity: sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==}
engines: {node: ^18.18.0 || >=20.0.0}
dev: true
/@typescript-eslint/types@7.2.0:
resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -6899,11 +6949,6 @@ packages:
engines: {node: ^18.18.0 || >=20.0.0}
dev: true
/@typescript-eslint/types@7.7.0:
resolution: {integrity: sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==}
engines: {node: ^18.18.0 || >=20.0.0}
dev: true
/@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5):
resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -6947,6 +6992,28 @@ packages:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@7.12.0(typescript@5.4.5):
resolution: {integrity: sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 7.12.0
'@typescript-eslint/visitor-keys': 7.12.0
debug: 4.3.4(supports-color@5.5.0)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.4
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.4.5)
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.5):
resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -6991,28 +7058,6 @@ packages:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@7.7.0(typescript@5.4.5):
resolution: {integrity: sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 7.7.0
'@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4(supports-color@5.5.0)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.4
semver: 7.6.0
ts-api-utils: 1.3.0(typescript@5.4.5)
typescript: 5.4.5
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.4.5):
resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7087,6 +7132,14 @@ packages:
eslint-visitor-keys: 3.4.3
dev: true
/@typescript-eslint/visitor-keys@7.12.0:
resolution: {integrity: sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==}
engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
'@typescript-eslint/types': 7.12.0
eslint-visitor-keys: 3.4.3
dev: true
/@typescript-eslint/visitor-keys@7.2.0:
resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -7103,15 +7156,7 @@ packages:
eslint-visitor-keys: 3.4.3
dev: true
/@typescript-eslint/visitor-keys@7.7.0:
resolution: {integrity: sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==}
engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
'@typescript-eslint/types': 7.7.0
eslint-visitor-keys: 3.4.3
dev: true
/@uiw/codemirror-extensions-basic-setup@4.21.25(@codemirror/autocomplete@6.16.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.1)(@codemirror/lint@6.7.1)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3):
/@uiw/codemirror-extensions-basic-setup@4.21.25(@codemirror/autocomplete@6.16.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.1)(@codemirror/lint@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3):
resolution: {integrity: sha512-eeUKlmEE8aSoSgelS8OR2elcPGntpRo669XinAqPCLa0eKorT2B0d3ts+AE+njAeGk744tiyAEbHb2n+6OQmJw==}
peerDependencies:
'@codemirror/autocomplete': '>=6.0.0'
@@ -7125,7 +7170,7 @@ packages:
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
'@codemirror/commands': 6.3.3
'@codemirror/language': 6.10.1
'@codemirror/lint': 6.7.1
'@codemirror/lint': 6.8.0
'@codemirror/search': 6.5.6
'@codemirror/state': 6.4.1
'@codemirror/view': 6.26.3
@@ -7175,7 +7220,7 @@ packages:
'@codemirror/view': 6.26.3
dev: false
/@uiw/react-codemirror@4.21.25(@babel/runtime@7.24.5)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.7.1)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0):
/@uiw/react-codemirror@4.21.25(@babel/runtime@7.24.5)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-mBrCoiffQ+hbTqV1JoixFEcH7BHXkS3PjTyNH7dE8Gzf3GSBRazhtSM5HrAFIiQ5FIRGFs8Gznc4UAdhtevMmw==}
peerDependencies:
'@babel/runtime': '>=7.11.0'
@@ -7191,7 +7236,7 @@ packages:
'@codemirror/state': 6.4.1
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.26.3
'@uiw/codemirror-extensions-basic-setup': 4.21.25(@codemirror/autocomplete@6.16.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.1)(@codemirror/lint@6.7.1)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)
'@uiw/codemirror-extensions-basic-setup': 4.21.25(@codemirror/autocomplete@6.16.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.1)(@codemirror/lint@6.8.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)
codemirror: 6.0.1(@lezer/common@1.2.1)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -7227,14 +7272,14 @@ packages:
'@babel/core': 7.24.3
'@babel/eslint-parser': 7.24.1(@babel/core@7.24.3)(eslint@8.57.0)
'@rushstack/eslint-patch': 1.7.2
'@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.12.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0
eslint-config-prettier: 9.1.0(eslint@8.57.0)
eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.29.1)
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.12.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.3.1)(eslint@8.57.0)(typescript@5.4.5)
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
eslint-plugin-playwright: 1.5.4(eslint-plugin-jest@27.9.0)(eslint@8.57.0)
@@ -8409,7 +8454,7 @@ packages:
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
'@codemirror/commands': 6.5.0
'@codemirror/language': 6.10.1
'@codemirror/lint': 6.7.1
'@codemirror/lint': 6.8.0
'@codemirror/search': 6.5.6
'@codemirror/state': 6.4.1
'@codemirror/view': 6.26.3
@@ -9510,7 +9555,7 @@ packages:
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
eslint-plugin-react: 7.34.1(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0)
@@ -9539,7 +9584,7 @@ packages:
eslint-plugin-promise: ^6.0.0
dependencies:
eslint: 8.57.0
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-n: 16.6.2(eslint@8.57.0)
eslint-plugin-promise: 6.1.1(eslint@8.57.0)
dev: true
@@ -9559,7 +9604,7 @@ packages:
peerDependencies:
eslint-plugin-import: '>=1.4.0'
dependencies:
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
dev: true
/eslint-import-resolver-node@0.3.9:
@@ -9572,6 +9617,29 @@ packages:
- supports-color
dev: true
/eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.12.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
eslint: '*'
eslint-plugin-import: '*'
dependencies:
debug: 4.3.4(supports-color@5.5.0)
enhanced-resolve: 5.16.0
eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.3
is-core-module: 2.13.1
is-glob: 4.0.3
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -9583,7 +9651,7 @@ packages:
enhanced-resolve: 5.16.0
eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.3
is-core-module: 2.13.1
@@ -9595,26 +9663,33 @@ packages:
- supports-color
dev: true
/eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
engines: {node: ^14.18.0 || >=16.0.0}
/eslint-module-utils@2.8.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: '*'
eslint-plugin-import: '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint:
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
dependencies:
debug: 4.3.4(supports-color@5.5.0)
enhanced-resolve: 5.16.0
'@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5)
debug: 3.2.7
eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.3
is-core-module: 2.13.1
is-glob: 4.0.3
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.12.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
- eslint-import-resolver-webpack
- supports-color
dev: true
@@ -9648,36 +9723,6 @@ packages:
- supports-color
dev: true
/eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint:
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
debug: 3.2.7
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
dev: true
/eslint-plugin-es-x@7.6.0(eslint@8.57.0):
resolution: {integrity: sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -9701,7 +9746,7 @@ packages:
ignore: 5.3.1
dev: true
/eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
/eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
engines: {node: '>=4'}
peerDependencies:
@@ -9711,7 +9756,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5)
array-includes: 3.1.7
array.prototype.findlastindex: 1.2.4
array.prototype.flat: 1.3.2
@@ -9720,7 +9765,7 @@ packages:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.12.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.13.1
is-glob: 4.0.3
@@ -9749,7 +9794,7 @@ packages:
jest:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.12.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0
transitivePeerDependencies:
@@ -9957,7 +10002,7 @@ packages:
vitest:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.12.0)(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0
transitivePeerDependencies:
@@ -12196,12 +12241,6 @@ packages:
engines: {node: '>=14'}
dev: false
/js-tiktoken@1.0.11:
resolution: {integrity: sha512-PajXFLq2vx7/8jllQZ43vzNpAai/0MOVdJjW/UrNyJorNQRTjHrqdGJG/mjHVy7h9M6dW6CaG43eNLMYFkTh6w==}
dependencies:
base64-js: 1.5.1
dev: false
/js-tiktoken@1.0.12:
resolution: {integrity: sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ==}
dependencies:
@@ -12407,256 +12446,6 @@ packages:
resolution: {integrity: sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==}
engines: {node: '>=14.0.0'}
/langchain@0.1.36:
resolution: {integrity: sha512-NTbnCL/jKWIeEI//Nm1oG8nhW3vkYWvEMr1MPotmTThTfeKfO87eV/OAzAyh6Ruy6GFs/qofRgQZGIe6XvXTNQ==}
engines: {node: '>=18'}
peerDependencies:
'@aws-sdk/client-s3': ^3.310.0
'@aws-sdk/client-sagemaker-runtime': ^3.310.0
'@aws-sdk/client-sfn': ^3.310.0
'@aws-sdk/credential-provider-node': ^3.388.0
'@azure/storage-blob': ^12.15.0
'@gomomento/sdk': ^1.51.1
'@gomomento/sdk-core': ^1.51.1
'@gomomento/sdk-web': ^1.51.1
'@google-ai/generativelanguage': ^0.2.1
'@google-cloud/storage': ^6.10.1 || ^7.7.0
'@mendable/firecrawl-js': ^0.0.13
'@notionhq/client': ^2.2.10
'@pinecone-database/pinecone': '*'
'@supabase/supabase-js': ^2.10.0
'@vercel/kv': ^0.2.3
'@xata.io/client': ^0.28.0
apify-client: ^2.7.1
assemblyai: ^4.0.0
axios: '*'
cheerio: ^1.0.0-rc.12
chromadb: '*'
convex: ^1.3.1
couchbase: ^4.3.0
d3-dsv: ^2.0.0
epub2: ^3.0.1
faiss-node: '*'
fast-xml-parser: '*'
google-auth-library: ^8.9.0
handlebars: ^4.7.8
html-to-text: ^9.0.5
ignore: ^5.2.0
ioredis: ^5.3.2
jsdom: '*'
mammoth: ^1.6.0
mongodb: '>=5.2.0'
node-llama-cpp: '*'
notion-to-md: ^3.1.0
officeparser: ^4.0.4
pdf-parse: 1.1.1
peggy: ^3.0.2
playwright: ^1.32.1
puppeteer: ^19.7.2
pyodide: ^0.24.1
redis: ^4.6.4
sonix-speech-recognition: ^2.1.1
srt-parser-2: ^1.2.3
typeorm: ^0.3.12
weaviate-ts-client: '*'
web-auth-library: ^1.0.3
ws: ^8.14.2
youtube-transcript: ^1.0.6
youtubei.js: ^9.1.0
peerDependenciesMeta:
'@aws-sdk/client-s3':
optional: true
'@aws-sdk/client-sagemaker-runtime':
optional: true
'@aws-sdk/client-sfn':
optional: true
'@aws-sdk/credential-provider-node':
optional: true
'@azure/storage-blob':
optional: true
'@gomomento/sdk':
optional: true
'@gomomento/sdk-core':
optional: true
'@gomomento/sdk-web':
optional: true
'@google-ai/generativelanguage':
optional: true
'@google-cloud/storage':
optional: true
'@mendable/firecrawl-js':
optional: true
'@notionhq/client':
optional: true
'@pinecone-database/pinecone':
optional: true
'@supabase/supabase-js':
optional: true
'@vercel/kv':
optional: true
'@xata.io/client':
optional: true
apify-client:
optional: true
assemblyai:
optional: true
axios:
optional: true
cheerio:
optional: true
chromadb:
optional: true
convex:
optional: true
couchbase:
optional: true
d3-dsv:
optional: true
epub2:
optional: true
faiss-node:
optional: true
fast-xml-parser:
optional: true
google-auth-library:
optional: true
handlebars:
optional: true
html-to-text:
optional: true
ignore:
optional: true
ioredis:
optional: true
jsdom:
optional: true
mammoth:
optional: true
mongodb:
optional: true
node-llama-cpp:
optional: true
notion-to-md:
optional: true
officeparser:
optional: true
pdf-parse:
optional: true
peggy:
optional: true
playwright:
optional: true
puppeteer:
optional: true
pyodide:
optional: true
redis:
optional: true
sonix-speech-recognition:
optional: true
srt-parser-2:
optional: true
typeorm:
optional: true
weaviate-ts-client:
optional: true
web-auth-library:
optional: true
ws:
optional: true
youtube-transcript:
optional: true
youtubei.js:
optional: true
dependencies:
'@anthropic-ai/sdk': 0.9.1
'@langchain/community': 0.0.47(@aws-sdk/credential-provider-node@3.549.0)(lodash@4.17.21)
'@langchain/core': 0.1.61
'@langchain/openai': 0.0.33
'@langchain/textsplitters': 0.0.0
binary-extensions: 2.3.0
js-tiktoken: 1.0.11
js-yaml: 4.1.0
jsonpointer: 5.0.1
langchainhub: 0.0.8
langsmith: 0.1.14
ml-distance: 4.0.1
openapi-types: 12.1.3
p-retry: 4.6.2
uuid: 9.0.1
yaml: 2.4.1
zod: 3.22.4
zod-to-json-schema: 3.22.5(zod@3.22.4)
transitivePeerDependencies:
- '@aws-crypto/sha256-js'
- '@aws-sdk/client-bedrock-agent-runtime'
- '@aws-sdk/client-bedrock-runtime'
- '@aws-sdk/client-dynamodb'
- '@aws-sdk/client-kendra'
- '@aws-sdk/client-lambda'
- '@azure/search-documents'
- '@clickhouse/client'
- '@cloudflare/ai'
- '@datastax/astra-db-ts'
- '@elastic/elasticsearch'
- '@getmetal/metal-sdk'
- '@getzep/zep-js'
- '@gradientai/nodejs-sdk'
- '@huggingface/inference'
- '@mozilla/readability'
- '@opensearch-project/opensearch'
- '@planetscale/database'
- '@premai/prem-sdk'
- '@qdrant/js-client-rest'
- '@raycast/api'
- '@rockset/client'
- '@smithy/eventstream-codec'
- '@smithy/protocol-http'
- '@smithy/signature-v4'
- '@smithy/util-utf8'
- '@supabase/postgrest-js'
- '@tensorflow-models/universal-sentence-encoder'
- '@tensorflow/tfjs-converter'
- '@tensorflow/tfjs-core'
- '@upstash/redis'
- '@upstash/vector'
- '@vercel/postgres'
- '@writerai/writer-sdk'
- '@xenova/transformers'
- '@zilliz/milvus2-sdk-node'
- better-sqlite3
- cassandra-driver
- cborg
- closevector-common
- closevector-node
- closevector-web
- cohere-ai
- discord.js
- dria
- duck-duck-scrape
- encoding
- firebase-admin
- googleapis
- hnswlib-node
- interface-datastore
- it-all
- jsonwebtoken
- llmonitor
- lodash
- lunary
- mysql2
- neo4j-driver
- pg
- pg-copy-streams
- pickleparser
- portkey-ai
- replicate
- typesense
- usearch
- vectordb
- voy-search
dev: false
/langchain@0.1.36(@aws-sdk/client-s3@3.550.0)(@aws-sdk/credential-provider-node@3.549.0)(lodash@4.17.21):
resolution: {integrity: sha512-NTbnCL/jKWIeEI//Nm1oG8nhW3vkYWvEMr1MPotmTThTfeKfO87eV/OAzAyh6Ruy6GFs/qofRgQZGIe6XvXTNQ==}
engines: {node: '>=18'}
@@ -16419,8 +16208,8 @@ packages:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: true
/tiktoken@1.0.13:
resolution: {integrity: sha512-JaL9ZnvTbGFMDIBeGdVkLt4qWTeCPw+n7Ock+wceAGRenuHA6nOOvMJFliNDyXsjg2osGKJWsXtO2xc74VxyDw==}
/tiktoken@1.0.15:
resolution: {integrity: sha512-sCsrq/vMWUSEW29CJLNmPvWxlVp7yh2tlkAjpJltIKqp5CKf98ZNpdeHRmAlPVFlGEbswDc6SmI8vz64W/qErw==}
dev: false
/tiny-invariant@1.3.3:
@@ -17039,6 +16828,20 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/vaul@0.9.1(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
dev: false
/victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
dependencies:
+1 -1
View File
@@ -5,7 +5,7 @@ FROM node:20-alpine AS alpine
RUN apk update && apk upgrade --no-cache libcrypto3 libssl3 libc6-compat
FROM alpine AS base
RUN npm install turbo --global
RUN npm install turbo@^1.13.3 --global
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "2.46.0",
"version": "2.49.2",
"private": true,
"license": "MIT",
"engines": {
@@ -25,6 +25,7 @@
"@aws-sdk/lib-storage": "^3.568.0",
"@aws-sdk/s3-request-presigner": "^3.554.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lint": "^6.8.0",
"@headlessui/react": "1.7.18",
"@headlessui/tailwindcss": "0.2.0",
"@heroicons/react": "^2.1.3",
@@ -55,6 +56,7 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@react-email/components": "^0.0.18",
"@react-email/render": "^0.0.14",
@@ -69,7 +71,6 @@
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.11.8",
"@tanstack/react-virtual": "^3.5.0",
"@tremor/react": "3.16.2",
"@trpc/client": "^10.45.0",
"@trpc/next": "^10.45.0",
@@ -115,6 +116,7 @@
"tailwindcss-animate": "^1.0.7",
"use-query-params": "^2.2.1",
"uuid": "^9.0.1",
"vaul": "^0.9.1",
"zod": "^3.22.4"
},
"devDependencies": {
@@ -136,7 +138,7 @@
"@types/react-dom": "^18.2.25",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/parser": "^7.12.0",
"autoprefixer": "^10.4.19",
"dotenv-cli": "^7.4.2",
"eslint": "^8.56.0",
+48 -1
View File
@@ -1137,7 +1137,10 @@ paths:
security: *ref_0
/api/public/sessions/{sessionId}:
get:
description: Get a session
description: >-
Get a session. Please note that `traces` on this endpoint are not
paginated, if you plan to fetch large sessions, consider `GET
/api/public/traces?sessionId=<sessionId>`
operationId: sessions_get
tags:
- Sessions
@@ -1262,6 +1265,12 @@ paths:
schema:
type: string
nullable: true
- name: sessionId
in: query
required: false
schema:
type: string
nullable: true
- name: fromTimestamp
in: query
description: Retrieve only traces newer than this datetime (ISO 8601).
@@ -2499,6 +2508,7 @@ components:
totalCost:
type: number
format: double
description: Total model cost in USD
usage:
type: array
items:
@@ -2521,14 +2531,28 @@ components:
nullable: true
inputUsage:
type: integer
description: Total number of generation input units (e.g. tokens)
outputUsage:
type: integer
description: Total number of generation output units (e.g. tokens)
totalUsage:
type: integer
description: Total number of generation total units (e.g. tokens)
countTraces:
type: integer
countObservations:
type: integer
totalCost:
type: number
format: double
description: Total model cost in USD
required:
- inputUsage
- outputUsage
- totalUsage
- countTraces
- countObservations
- totalCost
Observations:
title: Observations
type: object
@@ -2654,6 +2678,13 @@ components:
items:
type: string
nullable: true
description: List of deployment labels of this prompt version.
tags:
type: array
items:
type: string
nullable: true
description: List of tags to apply to all versions of this prompt.
required:
- name
- prompt
@@ -2672,6 +2703,13 @@ components:
items:
type: string
nullable: true
description: List of deployment labels of this prompt version.
tags:
type: array
items:
type: string
nullable: true
description: List of tags to apply to all versions of this prompt.
required:
- name
- prompt
@@ -2713,11 +2751,20 @@ components:
type: array
items:
type: string
description: List of deployment labels of this prompt version.
tags:
type: array
items:
type: string
description: >-
List of tags. Used to filter via UI and API. The same across
versions of a prompt.
required:
- name
- version
- config
- labels
- tags
ChatMessage:
title: ChatMessage
type: object
+8 -3
View File
@@ -699,7 +699,7 @@
"auth": null,
"body": {
"mode": "raw",
"raw": "{\n \"type\": \"chat\",\n \"name\": \"example\",\n \"prompt\": [\n {\n \"role\": \"example\",\n \"content\": \"example\"\n }\n ],\n \"config\": \"UNKNOWN\",\n \"labels\": [\n \"example\"\n ]\n}",
"raw": "{\n \"type\": \"chat\",\n \"name\": \"example\",\n \"prompt\": [\n {\n \"role\": \"example\",\n \"content\": \"example\"\n }\n ],\n \"config\": \"UNKNOWN\",\n \"labels\": [\n \"example\"\n ],\n \"tags\": [\n \"example\"\n ]\n}",
"options": {
"raw": {
"language": "json"
@@ -890,7 +890,7 @@
"_type": "endpoint",
"name": "Get",
"request": {
"description": "Get a session",
"description": "Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=<sessionId>`",
"url": {
"raw": "{{baseUrl}}/api/public/sessions/:sessionId",
"host": [
@@ -963,7 +963,7 @@
"request": {
"description": "Get list of traces.",
"url": {
"raw": "{{baseUrl}}/api/public/traces?page=&limit=&userId=&name=&fromTimestamp=&orderBy=&tags=",
"raw": "{{baseUrl}}/api/public/traces?page=&limit=&userId=&name=&sessionId=&fromTimestamp=&orderBy=&tags=",
"host": [
"{{baseUrl}}"
],
@@ -993,6 +993,11 @@
"value": "",
"description": null
},
{
"key": "sessionId",
"value": "",
"description": null
},
{
"key": "fromTimestamp",
"value": "",
+5 -5
View File
@@ -2,7 +2,7 @@ import { prisma } from "@langfuse/shared/src/db";
import * as Sentry from "@sentry/nextjs";
import { ProfilingIntegration } from "@sentry/profiling-node";
import type { SamplingContext, TransactionEvent } from "@sentry/types";
import { jsonSchema } from "@/src/utils/zod";
import { jsonSchema } from "@langfuse/shared";
if (process.env.NEXT_PUBLIC_SENTRY_DSN)
Sentry.init({
@@ -18,7 +18,7 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
samplingContext.request.url &&
samplingContext.request.url.includes("api/trpc")
) {
return 0.3;
return 0.1;
}
if (
samplingContext.request &&
@@ -27,12 +27,12 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
samplingContext.transactionContext.status !== "ok" &&
samplingContext.transactionContext.status !== "unauthenticated"
) {
return 1;
return 0.1;
}
return 0.1;
return 0.01;
},
profilesSampleRate: 0.2, // Profiling sample rate is relative to tracesSampleRate
profilesSampleRate: 0.1,
integrations: [
// Add profiling integration to list of integrations
new ProfilingIntegration(),
@@ -68,12 +68,18 @@ describe("/api/public/metrics/daily API Endpoint", () => {
inputUsage: 333,
outputUsage: 0,
totalUsage: 333,
countObservations: 1,
countTraces: 1,
totalCost: 0,
},
{
model: "modelC",
inputUsage: 666,
outputUsage: 777,
totalUsage: 1443,
countObservations: 1,
countTraces: 1,
totalCost: 1024.22,
},
]);
@@ -88,6 +94,9 @@ describe("/api/public/metrics/daily API Endpoint", () => {
inputUsage: 100,
outputUsage: 200,
totalUsage: 300,
countObservations: 1,
countTraces: 1,
totalCost: 0,
},
]);
});
@@ -680,6 +680,71 @@ describe("/api/public/v2/prompts API Endpoint", () => {
expect(fetchedPrompt.body.createdBy).toBe("API");
expect(fetchedPrompt.body.config).toEqual({});
});
it("should update tags across versions", async () => {
const promptName = "prompt-name" + nanoid();
const createPromptVersion = async (tags?: string[]) => {
await makeAPICall("POST", baseURI, {
name: promptName,
prompt: "This is a test prompt",
type: PromptType.Text,
...(tags !== undefined && { tags: tags }),
});
};
const fetchPromptVersion = async (version: number) => {
const fetchedPrompt = await makeAPICall(
"GET",
`${baseURI}/${promptName}?version=${version}`,
undefined,
);
expect(fetchedPrompt.status).toBe(200);
if (!isPrompt(fetchedPrompt.body)) {
throw new Error("Expected body to be a prompt");
}
return fetchedPrompt.body;
};
// Create version 1 with ["tag"]
await createPromptVersion(["tag"]);
let fetchedPrompt1 = await fetchPromptVersion(1);
expect(fetchedPrompt1.tags).toEqual(["tag"]);
expect(fetchedPrompt1.version).toBe(1);
// Create version 2 with no tags provided (should use tags from version 1)
await createPromptVersion();
let fetchedPrompt2 = await fetchPromptVersion(2);
expect(fetchedPrompt2.tags).toEqual(["tag"]);
expect(fetchedPrompt2.version).toBe(2);
// Create version 3 with ["tag1", "tag2", "tag3"] (should update tags across versions)
await createPromptVersion(["tag1", "tag2", "tag3"]);
fetchedPrompt1 = await fetchPromptVersion(1);
fetchedPrompt2 = await fetchPromptVersion(2);
let fetchedPrompt3 = await fetchPromptVersion(3);
expect(fetchedPrompt1.tags).toEqual(["tag1", "tag2", "tag3"]);
expect(fetchedPrompt1.version).toBe(1);
expect(fetchedPrompt2.tags).toEqual(["tag1", "tag2", "tag3"]);
expect(fetchedPrompt2.version).toBe(2);
expect(fetchedPrompt3.tags).toEqual(["tag1", "tag2", "tag3"]);
expect(fetchedPrompt3.version).toBe(3);
// remove tags
await createPromptVersion([]);
fetchedPrompt1 = await fetchPromptVersion(1);
fetchedPrompt2 = await fetchPromptVersion(2);
fetchedPrompt3 = await fetchPromptVersion(3);
let fetchedPrompt4 = await fetchPromptVersion(4);
expect(fetchedPrompt1.tags).toEqual([]);
expect(fetchedPrompt1.version).toBe(1);
expect(fetchedPrompt2.tags).toEqual([]);
expect(fetchedPrompt2.version).toBe(2);
expect(fetchedPrompt3.tags).toEqual([]);
expect(fetchedPrompt3.version).toBe(3);
expect(fetchedPrompt4.tags).toEqual([]);
expect(fetchedPrompt4.version).toBe(4);
});
});
describe("when fetching a prompt list", () => {
@@ -50,7 +50,7 @@ describe("Build valid SQL queries", () => {
table: "traces_scores",
values: ["project-id"],
strings: [
' FROM traces t JOIN scores s ON t.id = s.trace_id AND t.project_id = s.project_id WHERE t."project_id" = ',
` FROM traces t JOIN scores s ON t.id = s.trace_id AND s.data_type != 'CATEGORICAL' AND t.project_id = s.project_id WHERE t."project_id" = `,
";",
],
} as const,
+58
View File
@@ -238,4 +238,62 @@ describe("/api/public/traces API Endpoint", () => {
expect(trace.body.htmlPath).toContain(`/traces/${traceId}`);
expect(trace.body.htmlPath).toContain(`/project/`); // do not know the projectId
});
it("should filter traces by session ID", async () => {
const sessionId = "test-session-id";
const anotherSessionId = "another-session-id";
// Create traces with different session IDs
await makeAPICall("POST", "/api/public/traces", {
id: "trace-1",
name: "test-trace-1",
sessionId,
userId: "user-1",
projectId: "project-1",
metadata: { key: "value" },
release: "1.0.0",
version: "1.0.0",
});
await makeAPICall("POST", "/api/public/traces", {
id: "trace-2",
name: "test-trace-2",
sessionId: anotherSessionId,
userId: "user-2",
projectId: "project-1",
metadata: { key: "value" },
release: "1.0.0",
version: "1.0.0",
});
// Filter by session ID
const tracesBySessionId = await makeAPICall<GetTracesAPIResponse>(
"GET",
`/api/public/traces?sessionId=${sessionId}`,
);
expect(tracesBySessionId.status).toBe(200);
expect(tracesBySessionId.body.data).toHaveLength(1);
expect(tracesBySessionId.body.data[0].id).toBe("trace-1");
// Filter by another session ID
const tracesByAnotherSessionId = await makeAPICall<GetTracesAPIResponse>(
"GET",
`/api/public/traces?sessionId=${anotherSessionId}`,
);
expect(tracesByAnotherSessionId.status).toBe(200);
expect(tracesByAnotherSessionId.body.data).toHaveLength(1);
expect(tracesByAnotherSessionId.body.data[0].id).toBe("trace-2");
// Filter by non-existent session ID
const tracesByNonExistentSessionId =
await makeAPICall<GetTracesAPIResponse>(
"GET",
`/api/public/traces?sessionId=non-existent-session-id`,
);
expect(tracesByNonExistentSessionId.status).toBe(200);
expect(tracesByNonExistentSessionId.body.data).toHaveLength(0);
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
import { paginationZod } from "@/src/utils/zod";
import { paginationZod } from "@langfuse/shared";
import { ZodError } from "zod";
// Create test cases
+100 -13
View File
@@ -1,8 +1,105 @@
import { AlertTriangle, Check } from "lucide-react";
import {
AlertTriangle,
Check,
Github,
HardDriveDownload,
Newspaper,
} from "lucide-react";
import { VERSION } from "@/src/constants";
import { env } from "@/src/env.mjs";
import { cn } from "@/src/utils/tailwind";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/src/components/ui/dropdown-menu";
import { ArrowUp } from "lucide-react";
import { api } from "@/src/utils/api";
import { Button } from "@/src/components/ui/button";
import Link from "next/link";
const VersionLabel = ({ className }: { className?: string }) => {
const checkUpdate = api.public.checkUpdate.useQuery(undefined, {
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
enabled: !env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION, // do not check for updates on Langfuse Cloud
onError: (error) => console.error("checkUpdate error", error), // do not render default error message
});
const hasUpdate =
!env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION &&
checkUpdate.data &&
checkUpdate.data.updateType;
const color =
checkUpdate.data?.updateType === "major"
? "text-dark-red"
: checkUpdate.data?.updateType === "minor"
? "text-dark-yellow"
: undefined;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="xs" className={className}>
{VERSION}
{hasUpdate && <ArrowUp className={`ml-1 h-3 w-3 ${color}`} />}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{hasUpdate ? (
<>
<DropdownMenuLabel>
New {checkUpdate.data?.updateType} version:{" "}
{checkUpdate.data?.latestRelease}
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
) : !env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION ? (
<>
<DropdownMenuLabel>This is the latest release</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
) : null}
<DropdownMenuItem asChild>
<Link
href="https://github.com/langfuse/langfuse/releases"
target="_blank"
>
<Github size={16} className="mr-2" />
Releases
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="https://langfuse.com/changelog" target="_blank">
<Newspaper size={16} className="mr-2" />
Changelog
</Link>
</DropdownMenuItem>
{hasUpdate && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
href="https://langfuse.com/docs/deployment/self-host#update"
target="_blank"
>
<HardDriveDownload size={16} className="mr-2" />
Update
</Link>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};
export const LangfuseIcon = ({
size = 32,
@@ -65,22 +162,12 @@ export const LangfuseLogo = ({
<span
className={cn(
"ml-2 font-mono font-semibold",
size === "sm" ? "text-sm" : "text-xl",
size === "sm" ? "text-sm" : "text-lg",
)}
>
Langfuse
</span>
{version && (
<a
href="https://github.com/langfuse/langfuse/releases"
target="_blank"
rel="noopener"
title="View releases on GitHub"
className="ml-2 text-xs text-muted-foreground"
>
{VERSION}
</a>
)}
{version && <VersionLabel className="ml-2" />}
</div>
</div>
);
+15 -22
View File
@@ -8,14 +8,7 @@ import {
CardTitle,
} from "@/src/components/ui/card";
import { chatAvailable, openChat } from "@/src/features/support-chat/chat";
import {
Book,
Calendar,
Github,
Mail,
MessageSquare,
Slack,
} from "lucide-react";
import { Book, Github, Mail, MessageSquare, Slack } from "lucide-react";
import Link from "next/link";
import { SiDiscord } from "react-icons/si";
@@ -30,7 +23,7 @@ const supportChannels = [
},
{
icon: Github,
title: "GitHub",
title: "GitHub Issues",
description:
"Create an issue on Github to report bugs or request new features.",
href: "https://github.com/langfuse/langfuse/issues/new/choose",
@@ -38,12 +31,11 @@ const supportChannels = [
primary: true,
},
{
icon: SiDiscord,
title: "Discord",
description:
"Get support from community and maintainers. Follow announcements to stay up to date with new features.",
href: "https://langfuse.com/discord",
buttonText: "Join Discord",
icon: Github,
title: "GitHub Support",
description: "Create a support ticket via GitHub discussions.",
href: "https://github.com/orgs/langfuse/discussions/categories/support",
buttonText: "Submit question",
primary: true,
},
{
@@ -57,24 +49,25 @@ const supportChannels = [
{
icon: MessageSquare,
title: "Chat",
description: "Get quick support directly from the team.",
description: "Get support directly from the team.",
onClick: () => openChat(),
available: chatAvailable,
buttonText: "Launch Chat",
},
{
icon: Calendar,
title: "Schedule call",
description: "Schedule a call with one of the founders.",
href: "https://cal.com/marc-kl/office-hours",
buttonText: "Schedule Call",
icon: SiDiscord,
title: "Discord",
description:
"Get support from community. Follow announcements to stay up to date with new features.",
href: "https://langfuse.com/discord",
buttonText: "Join Discord",
},
{
icon: Slack,
title: "Slack Connect",
description: "Get a dedicated support channel for you and your team.",
href: "mailto:help@langfuse.com?subject=Slack%20Connect%20Request&body=I'd%20like%20to%20request%20a%20dedicated%20Slack%20Connect%20channel%20for%20me%20and%20my%20team.%0D%0A%0D%0AUsers%20(emails)%20to%20include%20besides%20mine%3A%0D%0A%0D%0A",
buttonText: "Request Slack Connect (via Email)",
buttonText: "Request via Email",
},
];
+1 -4
View File
@@ -53,16 +53,13 @@ export function DeleteButton({
},
});
if (!hasAccess) {
return null;
}
return (
<Popover key={itemId}>
<PopoverTrigger asChild>
<Button
variant={isTableAction ? "ghost" : "outline"}
size={isTableAction ? "xs" : "icon"}
disabled={!hasAccess}
onClick={() =>
type === "trace"
? capture("trace:delete_form_open", {
+14 -5
View File
@@ -4,9 +4,13 @@ import {
HoverCardContent,
HoverCardTrigger,
} from "@/src/components/ui/hover-card";
import {
isBooleanDataType,
isCategoricalDataType,
} from "@/src/features/manual-scoring/lib/helpers";
import { type ScoreSimplified } from "@/src/server/api/routers/generations/getAllQuery";
import { cn } from "@/src/utils/tailwind";
import { MessageCircle } from "lucide-react";
import { MessageCircleMore } from "lucide-react";
export const GroupedScoreBadges = ({
scores,
@@ -34,11 +38,13 @@ export const GroupedScoreBadges = ({
<div className={cn("text-xs", props.className)}>
{props.scores.map((s, i) => (
<span key={i} className="group/score ml-1 first:ml-0">
{s.value.toFixed(2)}
{isCategoricalDataType(s.dataType) || isBooleanDataType(s.dataType)
? s.stringValue
: s.value.toFixed(2)}
{s.comment && (
<HoverCard>
<HoverCardTrigger className="ml-1 inline-block cursor-pointer">
<MessageCircle size={12} />
<MessageCircleMore size={12} />
</HoverCardTrigger>
<HoverCardContent className="overflow-hidden whitespace-normal break-normal">
<p>{s.comment}</p>
@@ -73,9 +79,12 @@ export const GroupedScoreBadges = ({
<Badge
variant="outline"
key={name}
className="break-all font-normal"
className="grid grid-cols-[1fr,auto] gap-1 font-normal"
>
{name}: <ScoresOfGroup scores={scores} className="ml-2" />
<p className="truncate" title={name}>
{name}:
</p>
<ScoresOfGroup scores={scores} />
</Badge>
))}
</>
+19 -5
View File
@@ -1,9 +1,11 @@
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
import { githubLight } from "@uiw/codemirror-theme-github";
import { tokyoNight } from "@uiw/codemirror-theme-tokyo-night";
import { json } from "@codemirror/lang-json";
import { json, jsonParseLinter } from "@codemirror/lang-json";
import { linter } from "@codemirror/lint";
import { useTheme } from "next-themes";
import { cn } from "@/src/utils/tailwind";
import { useState } from "react";
// todo: add json linting
@@ -20,8 +22,13 @@ export function JsonEditor({
lineWrapping?: boolean;
className?: string;
}) {
const { theme } = useTheme();
const codeMirrorTheme = theme === "dark" ? tokyoNight : githubLight;
const { resolvedTheme } = useTheme();
const codeMirrorTheme = resolvedTheme === "dark" ? tokyoNight : githubLight;
// used to disable linter when field is empty
const [linterEnabled, setLinterEnabled] = useState<boolean>(
!!defaultValue && defaultValue !== "",
);
return (
<CodeMirror
value={defaultValue}
@@ -30,9 +37,16 @@ export function JsonEditor({
foldGutter: true,
}}
lang={"json"}
extensions={[json(), ...(lineWrapping ? [EditorView.lineWrapping] : [])]}
extensions={[
json(),
...(linterEnabled ? [linter(jsonParseLinter())] : []),
...(lineWrapping ? [EditorView.lineWrapping] : []),
]}
defaultValue={defaultValue}
onChange={onChange}
onChange={(c) => {
if (onChange) onChange(c);
setLinterEnabled(c !== "");
}}
className={cn("overflow-hidden rounded-md border", className)}
editable={editable}
/>
+11 -5
View File
@@ -521,9 +521,9 @@ const MainNavigation: React.FC<{
onNavitemClick?: () => void;
className?: string;
}> = ({ nav, onNavitemClick, className }) => {
const [isOpen, setIsOpen] = useLocalStorage(
"sidebar-tracing-default-open",
false,
const [isOpen, setIsOpen] = useLocalStorage<Record<string, boolean>>(
"sidebar-item-default-open",
{},
);
return (
@@ -572,14 +572,20 @@ const MainNavigation: React.FC<{
<Disclosure
as="div"
defaultOpen={
item.children.some((child) => child.current) || isOpen
item.children.some((child) => child.current) ||
isOpen[item.name]
}
>
{({ open }) => (
<>
<Disclosure.Button
className="group flex w-full items-center gap-x-3 rounded-md p-2 text-left text-sm font-semibold hover:bg-primary-foreground hover:text-primary-accent"
onClick={() => setIsOpen(!isOpen)}
onClick={() =>
setIsOpen((prev) => ({
...prev,
[item.name]: !prev[item.name],
}))
}
>
{item.icon && (
<item.icon
+50 -118
View File
@@ -7,38 +7,24 @@ import { IOPreview } from "@/src/components/trace/IOPreview";
import { JsonSkeleton } from "@/src/components/ui/CodeJsonViewer";
import { Badge } from "@/src/components/ui/badge";
import { Card } from "@/src/components/ui/card";
import { ManualScoreButton } from "@/src/features/manual-scoring/components/ManualScoreButton";
import { DetailPageNav } from "@/src/features/navigate-detail-pages/DetailPageNav";
import { useDetailPageLists } from "@/src/features/navigate-detail-pages/context";
import { api } from "@/src/utils/api";
import { usdFormatter } from "@/src/utils/numbers";
import Link from "next/link";
import { useEffect, useRef } from "react";
import { useWindowVirtualizer } from "@tanstack/react-virtual";
import { type RouterOutput } from "@/src/utils/types";
import {
DataTableRowHeightSwitch,
type RowHeight,
useRowHeightLocalStorage,
} from "@/src/components/table/data-table-row-height-switch";
import { ScrollArea } from "@/src/components/ui/scroll-area";
import { useEffect, useState } from "react";
import { AnnotateButton } from "@/src/features/manual-scoring/components/AnnotateButton";
import { Button } from "@/src/components/ui/button";
// do not use the usual table row heights here
const rowHeightMapping: Record<RowHeight, number> = {
s: 200,
m: 350,
l: 700,
};
// some projects have thousands of traces in a sessions, paginate to avoid rendering all at once
const PAGE_SIZE = 50;
export const SessionPage: React.FC<{
sessionId: string;
projectId: string;
}> = ({ sessionId, projectId }) => {
const { setDetailPageList } = useDetailPageLists();
const [rowHeight, setRowHeight] = useRowHeightLocalStorage(
"single-session",
"m",
);
const [visibleTraces, setVisibleTraces] = useState(PAGE_SIZE);
const session = api.sessions.byId.useQuery(
{
sessionId,
@@ -96,11 +82,6 @@ export const SessionPage: React.FC<{
}
listKey="sessions"
/>,
<DataTableRowHeightSwitch
rowHeight={rowHeight}
setRowHeight={setRowHeight}
key="height"
/>,
]}
/>
<div className="flex flex-wrap gap-2">
@@ -121,97 +102,49 @@ export const SessionPage: React.FC<{
</Badge>
)}
</div>
{session.data && (
<TraceCardList
session={session.data}
projectId={projectId}
rowHeight={rowHeightMapping[rowHeight]}
/>
)}
</div>
);
};
const TraceCardList = ({
session,
projectId,
rowHeight,
}: {
session: RouterOutput["sessions"]["byId"];
projectId: string;
rowHeight: number;
}) => {
const listVirtualizationRef = useRef<HTMLDivElement | null>(null);
const virtualizer = useWindowVirtualizer({
count: session.traces.length,
estimateSize: () => rowHeight,
overscan: 5,
scrollMargin: listVirtualizationRef.current?.offsetTop ?? 0,
gap: 10,
});
useEffect(() => {
// re-measure when rowHeight changes to update the virtualizer
virtualizer.measure();
}, [rowHeight, virtualizer]);
return (
<div className="mt-5 border-t pt-5">
<div
ref={listVirtualizationRef}
style={{
height: `${virtualizer.getTotalSize()}px`,
position: "relative",
}}
>
{virtualizer
.getVirtualItems()
.map((virtualItem) => ({
virtualItem,
trace: session.traces[virtualItem.index],
}))
.map(({ virtualItem, trace }) => (
<Card
className="group grid w-full gap-3 overflow-hidden border-border p-2 shadow-none hover:border-ring md:grid-cols-3"
key={virtualItem.key}
data-index={virtualItem.index}
style={{
position: "absolute",
top: 0,
left: 0,
height: `${rowHeight}px`,
transform: `translateY(${virtualItem.start - virtualizer.options.scrollMargin}px)`,
}}
>
<ScrollArea className="col-span-2 pr-3">
<SessionIO traceId={trace.id} />
</ScrollArea>
<ScrollArea>
<div className="-mt-1 overflow-y-auto p-1 opacity-50 transition-opacity group-hover:opacity-100">
<Link
href={`/project/${projectId}/traces/${trace.id}`}
className="text-xs hover:underline"
>
Trace: {trace.name} ({trace.id})&nbsp;
</Link>
<div className="text-xs text-muted-foreground">
{trace.timestamp.toLocaleString()}
</div>
<div className="mb-1 mt-2 text-xs text-muted-foreground">
Scores
</div>
<div className="flex flex-wrap content-start items-start gap-1">
<GroupedScoreBadges scores={trace.scores} />
</div>
<ManualScoreButton
projectId={projectId}
traceId={trace.id}
scores={trace.scores}
variant="badge"
/>
</div>
</ScrollArea>
</Card>
))}
<div className="mt-5 flex flex-col gap-2 border-t pt-5">
{session.data?.traces.slice(0, visibleTraces).map((trace) => (
<Card
className="group grid gap-3 border-border p-2 shadow-none hover:border-ring md:grid-cols-3"
key={trace.id}
>
<SessionIO traceId={trace.id} />
<div className="-mt-1 p-1 opacity-50 transition-opacity group-hover:opacity-100">
<Link
href={`/project/${projectId}/traces/${trace.id}`}
className="text-xs hover:underline"
>
Trace: {trace.name} ({trace.id})&nbsp;
</Link>
<div className="text-xs text-muted-foreground">
{trace.timestamp.toLocaleString()}
</div>
<div className="mb-1 mt-2 text-xs text-muted-foreground">
Scores
</div>
<div className="mb-1 flex flex-wrap content-start items-start gap-1">
<GroupedScoreBadges scores={trace.scores} />
</div>
<AnnotateButton
projectId={projectId}
traceId={trace.id}
scores={trace.scores}
variant="badge"
type="session"
source="SessionDetail"
/>
</div>
</Card>
))}
{session.data?.traces && session.data.traces.length > visibleTraces && (
<Button
onClick={() => setVisibleTraces((prev) => prev + PAGE_SIZE)}
variant="ghost"
className="self-center"
>
{`Load ${Math.min(session.data.traces.length - visibleTraces, PAGE_SIZE)} More`}
</Button>
)}
</div>
</div>
);
@@ -230,9 +163,8 @@ const SessionIO = ({ traceId }: { traceId: string }) => {
refetchOnMount: false, // prevents refetching loops
},
);
return (
<div className="flex flex-col gap-2 overflow-x-hidden overflow-y-scroll p-0">
<div className="col-span-2 flex flex-col gap-2 p-0">
{!trace.data ? (
<JsonSkeleton
className="h-full w-full overflow-hidden px-2 py-1"
+3 -2
View File
@@ -7,6 +7,7 @@ import { cn } from "@/src/utils/tailwind";
import { type RouterOutput, type RouterInput } from "@/src/utils/types";
import { useState } from "react";
import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture";
import { trpcErrorToast } from "@/src/utils/trpcErrorToast";
export function StarToggle({
value,
@@ -76,7 +77,7 @@ export function StarTraceToggle({
onError: (err, _newTodo, context) => {
setIsLoading(false);
// Rollback to the previous value if mutation fails
console.log("error", err);
trpcErrorToast(err);
utils.traces.all.setData(tracesFilter, context?.prev);
},
onSettled: () => {
@@ -157,7 +158,7 @@ export function StarTraceDetailsToggle({
},
onError: (err, _newTodo, context) => {
setIsLoading(false);
console.log("error", err);
trpcErrorToast(err);
// Rollback to the previous value if mutation fails
utils.traces.byId.setData({ traceId }, context?.prevData);
},
+23 -3
View File
@@ -50,6 +50,8 @@ interface DataTableProps<TData, TValue> {
help?: { description: string; href: string };
rowHeight?: RowHeight;
className?: string;
paginationClassName?: string;
isBorderless?: boolean;
}
export interface AsyncTableData<T> {
@@ -71,6 +73,9 @@ export function DataTable<TData extends object, TValue>({
orderBy,
setOrderBy,
rowHeight,
className,
paginationClassName,
isBorderless = false,
}: DataTableProps<TData, TValue>) {
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const rowheighttw = getRowHeightTailwindClass(rowHeight);
@@ -105,8 +110,18 @@ export function DataTable<TData extends object, TValue>({
return (
<>
<div className="flex w-full max-w-full flex-1 flex-col gap-1 overflow-auto">
<div className="w-full overflow-auto rounded-md border">
<div
className={cn(
"flex w-full max-w-full flex-1 flex-col gap-1 overflow-auto",
className,
)}
>
<div
className={cn(
"w-full overflow-auto",
isBorderless ? "" : "rounded-md border",
)}
>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
@@ -242,7 +257,12 @@ export function DataTable<TData extends object, TValue>({
<div className="grow"></div>
</div>
{pagination !== undefined ? (
<div className="bg:background sticky bottom-0 z-10 flex w-full justify-end font-medium">
<div
className={cn(
"sticky bottom-0 z-10 flex w-full justify-end bg-background font-medium",
paginationClassName,
)}
>
<DataTablePagination
table={table}
paginationOptions={pagination.options}
@@ -21,11 +21,7 @@ import {
withDefault,
} from "use-query-params";
import { useQueryFilterState } from "@/src/features/filters/hooks/useFilterState";
import {
formatIntervalSeconds,
intervalInSeconds,
utcDateOffsetByDays,
} from "@/src/utils/dates";
import { formatIntervalSeconds, utcDateOffsetByDays } from "@/src/utils/dates";
import useColumnVisibility from "@/src/features/column-visibility/hooks/useColumnVisibility";
import { type LangfuseColumnDef } from "@/src/components/table/types";
import {
@@ -119,7 +115,7 @@ export default function GenerationsTable({
type: "datetime",
operator: ">",
value: utcDateOffsetByDays(
session.data?.environment.defaultTableDateTimeOffset ?? -14,
session.data?.environment.defaultTableDateTimeOffset ?? -7,
),
},
],
+54 -29
View File
@@ -1,6 +1,12 @@
import { DataTable } from "@/src/components/table/data-table";
import { type LangfuseColumnDef } from "@/src/components/table/types";
import { Button } from "@/src/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/src/components/ui/popover";
import { useState } from "react";
import useColumnVisibility from "@/src/features/column-visibility/hooks/useColumnVisibility";
import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture";
import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
@@ -10,6 +16,7 @@ import { type Prisma, type Model } from "@langfuse/shared/src/db";
import Decimal from "decimal.js";
import { Trash } from "lucide-react";
import { useQueryParams, withDefault, NumberParam } from "use-query-params";
import { cn } from "@/src/utils/tailwind";
export type ModelTableRow = {
modelId: string;
@@ -222,13 +229,12 @@ export default function ModelTable({ projectId }: { projectId: string }) {
accessorKey: "actions",
header: "Actions",
cell: ({ row }) => {
return row.original.maintainer === "User" ? (
return (
<DeleteModelButton
projectId={projectId}
modelId={row.original.modelId}
isBuiltIn={row.original.maintainer === "Langfuse"}
/>
) : (
<div className="h-6" />
);
},
},
@@ -287,10 +293,13 @@ export default function ModelTable({ projectId }: { projectId: string }) {
const DeleteModelButton = ({
modelId,
projectId,
isBuiltIn,
}: {
modelId: string;
projectId: string;
isBuiltIn?: boolean;
}) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const capture = usePostHogClientCapture();
const mut = api.models.delete.useMutation({
@@ -304,32 +313,48 @@ const DeleteModelButton = ({
scope: "models:CUD",
});
if (!hasAccess) {
return null;
}
return (
<Button
size="xs"
variant="ghost"
onClick={() => {
const confirmDelete = window.confirm(
"Are you sure you want to delete this model?",
);
if (confirmDelete) {
capture("models:delete_button_click");
mut
.mutateAsync({
projectId,
modelId,
})
.catch((error) => {
console.error(error);
});
}
}}
>
<Trash size={14} />
</Button>
<Popover open={isOpen} onOpenChange={() => setIsOpen(!isOpen)}>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="xs"
disabled={!hasAccess || isBuiltIn}
title={
isBuiltIn ? "Built-in models cannot be deleted" : "Delete model"
}
className={cn(
isBuiltIn &&
"disabled:pointer-events-auto disabled:cursor-not-allowed",
)}
>
<Trash className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent>
<h2 className="text-md mb-3 font-semibold">Please confirm</h2>
<p className="mb-3 text-sm">
This action permanently deletes this model definition.
</p>
<div className="flex justify-end space-x-4">
<Button
type="button"
variant="destructive"
loading={mut.isLoading}
onClick={() => {
capture("models:delete_button_click");
mut.mutateAsync({
projectId,
modelId,
});
setIsOpen(false);
}}
>
Delete Model
</Button>
</div>
</PopoverContent>
</Popover>
);
};
@@ -0,0 +1,174 @@
import React from "react";
import { Card } from "@/src/components/ui/card";
import { useRowHeightLocalStorage } from "@/src/components/table/data-table-row-height-switch";
import useColumnVisibility from "@/src/features/column-visibility/hooks/useColumnVisibility";
import { api } from "@/src/utils/api";
import { type LangfuseColumnDef } from "@/src/components/table/types";
import { DataTableToolbar } from "@/src/components/table/data-table-toolbar";
import { DataTable } from "@/src/components/table/data-table";
import { type ScoreDataType, type Prisma } from "@langfuse/shared";
import { IOTableCell } from "@/src/components/ui/CodeJsonViewer";
import { NumberParam, useQueryParams, withDefault } from "use-query-params";
import { isNumericDataType } from "@/src/features/manual-scoring/lib/helpers";
type ScoreConfigTableRow = {
id: string;
name: string;
dataType: ScoreDataType;
createdAt: string;
updatedAt: string;
range: {
maxValue?: number | null;
minValue?: number | null;
categories?: Prisma.JsonValue | null;
};
description?: string | null;
};
function getConfigRange(
originalRow: ScoreConfigTableRow,
): Prisma.JsonValue | undefined {
const { range, dataType } = originalRow;
if (isNumericDataType(dataType)) {
return [
{ minValue: range.minValue ?? "-∞", maxValue: range.maxValue ?? "∞" },
];
}
return range.categories;
}
export function ScoreConfigsTable({ projectId }: { projectId: string }) {
const [paginationState, setPaginationState] = useQueryParams({
pageIndex: withDefault(NumberParam, 0),
pageSize: withDefault(NumberParam, 50),
});
const [rowHeight, setRowHeight] = useRowHeightLocalStorage(
"scoreConfigs",
"s",
);
const configs = api.scoreConfigs.all.useQuery({
projectId,
page: paginationState.pageIndex,
limit: paginationState.pageSize,
});
const totalCount = configs.data?.totalCount ?? 0;
const columns: LangfuseColumnDef<ScoreConfigTableRow>[] = [
{
accessorKey: "name",
id: "name",
header: "Name",
enableHiding: true,
},
{
accessorKey: "dataType",
id: "dataType",
header: "Data Type",
enableHiding: true,
},
{
accessorKey: "range",
id: "range",
header: "Range",
enableHiding: true,
cell: ({ row }) => {
const range = getConfigRange(row.original);
return !!range ? (
<IOTableCell data={range} singleLine={rowHeight === "s"} />
) : null;
},
},
{
accessorKey: "description",
id: "description",
header: "Description",
enableHiding: true,
cell: ({ row }) => {
const value = row.original.description;
return !!value ? (
<IOTableCell data={value} singleLine={rowHeight === "s"} />
) : null;
},
},
{
accessorKey: "id",
id: "id",
header: "Config ID",
enableHiding: true,
defaultHidden: true,
},
{
accessorKey: "createdAt",
id: "createdAt",
header: "Created At",
enableHiding: true,
defaultHidden: true,
},
];
const [columnVisibility, setColumnVisibility] =
useColumnVisibility<ScoreConfigTableRow>(
"scoreConfigsColumnVisibility",
columns,
);
return (
<>
<DataTableToolbar
columns={columns}
columnVisibility={columnVisibility}
setColumnVisibility={setColumnVisibility}
rowHeight={rowHeight}
setRowHeight={setRowHeight}
/>
<Card className="mb-4 flex max-h-[calc(100dvh-40rem)] flex-col overflow-hidden">
<DataTable
columns={columns}
data={
configs.isLoading
? { isLoading: true, isError: false }
: configs.isError
? {
isLoading: false,
isError: true,
error: configs.error.message,
}
: {
isLoading: false,
isError: false,
data: configs.data?.configs.map((config) => ({
id: config.id,
name: config.name,
dataType: config.dataType,
description: config.description,
createdAt: config.createdAt.toLocaleString(),
updatedAt: config.updatedAt.toLocaleString(),
range: {
maxValue: config.maxValue,
minValue: config.minValue,
categories: config.categories,
},
})),
}
}
pagination={{
pageCount: Math.ceil(totalCount / paginationState.pageSize),
onChange: setPaginationState,
state: paginationState,
}}
columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility}
rowHeight={rowHeight}
className="gap-2"
paginationClassName="-mx-2 mb-2"
isBorderless
/>
</Card>
</>
);
}
+80 -29
View File
@@ -4,16 +4,20 @@ import { DataTableToolbar } from "@/src/components/table/data-table-toolbar";
import TableLink from "@/src/components/table/table-link";
import { type LangfuseColumnDef } from "@/src/components/table/types";
import { IOTableCell } from "@/src/components/ui/CodeJsonViewer";
import { Avatar, AvatarImage } from "@/src/components/ui/avatar";
import useColumnVisibility from "@/src/features/column-visibility/hooks/useColumnVisibility";
import { useQueryFilterState } from "@/src/features/filters/hooks/useFilterState";
import { isNumericDataType } from "@/src/features/manual-scoring/lib/helpers";
import { useOrderByState } from "@/src/features/orderBy/hooks/useOrderByState";
import {
type ScoreOptions,
scoresTableColsWithOptions,
} from "@/src/server/api/definitions/scoresTable";
import { api } from "@/src/utils/api";
import { utcDateOffsetByDays } from "@/src/utils/dates";
import type { RouterOutput, RouterInput } from "@/src/utils/types";
import type { FilterState } from "@langfuse/shared";
import type { FilterState, ScoreDataType } from "@langfuse/shared";
import { useSession } from "next-auth/react";
import { useQueryParams, withDefault, NumberParam } from "use-query-params";
export type ScoresTableRow = {
@@ -22,7 +26,12 @@ export type ScoresTableRow = {
timestamp: string;
source: string;
name: string;
value: number;
dataType: ScoreDataType;
value: string;
author: {
image?: string;
name?: string;
};
comment?: string;
observationId?: string;
traceName?: string;
@@ -72,11 +81,20 @@ export default function ScoresTable({
pageIndex: withDefault(NumberParam, 0),
pageSize: withDefault(NumberParam, 50),
});
const session = useSession();
const [rowHeight, setRowHeight] = useRowHeightLocalStorage("scores", "s");
const [userFilterState, setUserFilterState] = useQueryFilterState(
[],
[
{
column: "Timestamp",
type: "datetime",
operator: ">",
value: utcDateOffsetByDays(
session.data?.environment.defaultTableDateTimeOffset ?? -7,
),
},
],
"scores",
);
@@ -161,6 +179,29 @@ export default function ScoresTable({
) : undefined;
},
},
{
accessorKey: "userId",
header: "Trace User ID",
id: "userId",
headerTooltip: {
description: "The user ID associated with the trace.",
href: "https://langfuse.com/docs/tracing-features/users",
},
enableHiding: true,
enableSorting: true,
cell: ({ row }) => {
const value = row.getValue("userId");
return typeof value === "string" ? (
<>
<TableLink
path={`/project/${projectId}/users/${value}`}
value={value}
truncateAt={40}
/>
</>
) : undefined;
},
},
{
accessorKey: "timestamp",
header: "Timestamp",
@@ -182,38 +223,39 @@ export default function ScoresTable({
enableHiding: true,
enableSorting: true,
},
{
accessorKey: "dataType",
header: "Data Type",
id: "dataType",
enableHiding: true,
enableSorting: true,
},
{
accessorKey: "value",
header: "Value",
id: "value",
enableHiding: true,
enableSorting: true,
cell: ({ row }) => {
const value: number = row.getValue("value");
return value % 1 === 0 ? value : value.toFixed(4);
},
},
{
accessorKey: "userId",
header: "User ID",
id: "userId",
headerTooltip: {
description: "The user ID associated with the trace.",
href: "https://langfuse.com/docs/tracing-features/users",
},
enableHiding: true,
enableSorting: true,
accessorKey: "author",
id: "author",
header: "Author",
cell: ({ row }) => {
const value = row.getValue("userId");
return typeof value === "string" ? (
<>
<TableLink
path={`/project/${projectId}/users/${value}`}
value={value}
truncateAt={40}
/>
</>
) : undefined;
const { name, image } = row.getValue(
"author",
) as ScoresTableRow["author"];
return (
<div className="flex items-center space-x-2">
<Avatar className="h-7 w-7">
<AvatarImage
src={image ?? undefined}
alt={name ?? "User Avatar"}
/>
</Avatar>
<span>{name}</span>
</div>
);
},
},
{
@@ -268,12 +310,21 @@ export default function ScoresTable({
timestamp: score.timestamp.toLocaleString(),
source: score.source,
name: score.name,
value: score.value,
dataType: score.dataType,
value: isNumericDataType(score.dataType)
? score.value % 1 === 0
? String(score.value)
: score.value.toFixed(4)
: score.stringValue ?? "",
author: {
image: score.authorUserImage ?? undefined,
name: score.authorUserName ?? undefined,
},
comment: score.comment ?? undefined,
observationId: score.observationId ?? undefined,
traceId: score.traceId,
traceName: score.traceName ?? undefined,
userId: score.userId ?? undefined,
userId: score.traceUserId ?? undefined,
jobConfigurationId: score.jobConfigurationId ?? undefined,
};
};
@@ -55,7 +55,7 @@ export default function SessionsTable({
type: "datetime",
operator: ">",
value: utcDateOffsetByDays(
session.data?.environment.defaultTableDateTimeOffset ?? -14,
session.data?.environment.defaultTableDateTimeOffset ?? -7,
),
},
],
@@ -96,7 +96,7 @@ export default function TracesTable({
type: "datetime",
operator: ">",
value: utcDateOffsetByDays(
session.data?.environment.defaultTableDateTimeOffset ?? -14,
session.data?.environment.defaultTableDateTimeOffset ?? -7,
),
},
],
+1 -1
View File
@@ -1,6 +1,6 @@
import { JSONView } from "@/src/components/ui/CodeJsonViewer";
import { z } from "zod";
import { deepParseJson } from "@/src/utils/json";
import { deepParseJson } from "@langfuse/shared";
import { cn } from "@/src/utils/tailwind";
import { useState } from "react";
import { Button } from "@/src/components/ui/button";
@@ -8,7 +8,6 @@ import {
CardTitle,
} from "@/src/components/ui/card";
import { Badge } from "@/src/components/ui/badge";
import { ManualScoreButton } from "@/src/features/manual-scoring/components/ManualScoreButton";
import { NewDatasetItemFromTrace } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
import { type ObservationReturnType } from "@/src/server/api/routers/traces";
import { api } from "@/src/utils/api";
@@ -22,6 +21,7 @@ import { withDefault, StringParam, useQueryParam } from "use-query-params";
import ScoresTable from "@/src/components/table/use-cases/scores";
import { ScoresPreview } from "@/src/components/trace/ScoresPreview";
import { JumpToPlaygroundButton } from "@/src/ee/features/playground/page/components/JumpToPlaygroundButton";
import { AnnotateButton } from "@/src/features/manual-scoring/components/AnnotateButton";
export const ObservationPreview = (props: {
observations: Array<ObservationReturnType>;
@@ -154,11 +154,12 @@ export const ObservationPreview = (props: {
</div>
</div>
<div className="flex flex-wrap gap-2">
<ManualScoreButton
<AnnotateButton
projectId={props.projectId}
traceId={preloadedObservation.traceId}
observationId={preloadedObservation.id}
scores={props.scores}
type="observation"
/>
{observationWithInputAndOutput.data?.type === "GENERATION" && (
<JumpToPlaygroundButton
@@ -237,7 +238,7 @@ const PromptBadge = (props: { promptId: string; projectId: string }) => {
if (prompt.isLoading || !prompt.data) return null;
return (
<Link
href={`/project/${props.projectId}/prompts/${prompt.data.name}?version=${prompt.data.version}`}
href={`/project/${props.projectId}/prompts/${encodeURIComponent(prompt.data.name)}?version=${prompt.data.version}`}
>
<Badge>
Prompt: {prompt.data.name}
+4 -4
View File
@@ -9,7 +9,6 @@ import {
CardTitle,
} from "@/src/components/ui/card";
import { TraceAggUsageBadge } from "@/src/components/token-usage-badge";
import { ManualScoreButton } from "@/src/features/manual-scoring/components/ManualScoreButton";
import { Badge } from "@/src/components/ui/badge";
import { type ObservationReturnType } from "@/src/server/api/routers/traces";
import { IOPreview } from "@/src/components/trace/IOPreview";
@@ -19,6 +18,7 @@ import { Tabs, TabsList, TabsTrigger } from "@/src/components/ui/tabs";
import { withDefault, StringParam, useQueryParam } from "use-query-params";
import ScoresTable from "@/src/components/table/use-cases/scores";
import { ScoresPreview } from "@/src/components/trace/ScoresPreview";
import { AnnotateButton } from "@/src/features/manual-scoring/components/AnnotateButton";
export const TracePreview = ({
trace,
@@ -54,13 +54,13 @@ export const TracePreview = ({
<TabsList className="bg-background py-0">
<TabsTrigger
value="preview"
className="data-[state=active]:border-primary-accent h-full rounded-none border-b-4 border-transparent data-[state=active]:shadow-none"
className="h-full rounded-none border-b-4 border-transparent data-[state=active]:border-primary-accent data-[state=active]:shadow-none"
>
Preview
</TabsTrigger>
<TabsTrigger
value="scores"
className="data-[state=active]:border-primary-accent h-full rounded-none border-b-4 border-transparent data-[state=active]:shadow-none"
className="h-full rounded-none border-b-4 border-transparent data-[state=active]:border-primary-accent data-[state=active]:shadow-none"
>
Scores
</TabsTrigger>
@@ -95,7 +95,7 @@ export const TracePreview = ({
</div>
</div>
<div className="flex flex-wrap gap-2">
<ManualScoreButton
<AnnotateButton
projectId={trace.projectId}
traceId={trace.id}
scores={scores}
+1
View File
@@ -277,6 +277,7 @@ export function TracePage({ traceId }: { traceId: string }) {
availableTags={allTags}
traceId={trace.data.id}
projectId={trace.data.projectId}
className="flex-wrap"
/>
</div>
</div>
+3 -3
View File
@@ -4,7 +4,7 @@ import { Check, ChevronsDownUp, ChevronsUpDown, Copy } from "lucide-react";
import { cn } from "@/src/utils/tailwind";
import { default as React18JsonView } from "react18-json-view";
import "react18-json-view/src/dark.css";
import { deepParseJson } from "@/src/utils/json";
import { deepParseJson } from "@langfuse/shared";
import { Skeleton } from "@/src/components/ui/skeleton";
import { useTheme } from "next-themes";
@@ -17,7 +17,7 @@ export function JSONView(props: {
}) {
// some users ingest stringified json nested in json, parse it
const parsedJson = deepParseJson(props.json);
const { theme } = useTheme();
const { resolvedTheme } = useTheme();
return (
<div className={cn("rounded-md border", props.className)}>
{props.title ? (
@@ -44,7 +44,7 @@ export function JSONView(props: {
<React18JsonView
src={parsedJson}
theme="github"
dark={theme === "dark"}
dark={resolvedTheme === "dark"}
collapseObjectsAfterLength={20}
collapseStringsAfterLength={500}
displaySize={"collapsed"}
+2
View File
@@ -19,6 +19,8 @@ const buttonVariants = cva(
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
errorNotification:
"bg-destructive-foreground/90 text-destructive hover:bg-destructive-foreground/80",
},
size: {
default: "h-10 px-4 py-2",
+1 -1
View File
@@ -42,7 +42,7 @@ const CommandInput = React.forwardRef<
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div
className="flex items-center rounded-lg border px-3"
className="flex items-center rounded-md border px-3"
cmdk-input-wrapper=""
>
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
+129
View File
@@ -0,0 +1,129 @@
"use client";
import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/src/utils/tailwind";
import { useMediaQuery } from "react-responsive";
// https://tailwindcss.com/docs/responsive-design
const TAILWIND_MD_MEDIA_QUERY = 768;
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => {
const isMediumScreen = useMediaQuery({
query: `(min-width: ${TAILWIND_MD_MEDIA_QUERY}px)`,
});
const direction = isMediumScreen ? "right" : "bottom";
return (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
direction={direction}
{...props}
/>
);
};
Drawer.displayName = "Drawer";
const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-primary/20", className)}
{...props}
/>
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background md:inset-x-auto md:inset-y-0 md:right-0 md:mt-0 md:h-full md:w-2/5 md:rounded-l-[10px] md:rounded-t-[0px] lg:w-1/3 xl:w-1/4 2xl:w-1/4",
className,
)}
{...props}
>
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
);
DrawerHeader.displayName = "DrawerHeader";
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
);
DrawerFooter.displayName = "DrawerFooter";
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
};
+3 -1
View File
@@ -28,4 +28,6 @@ const PopoverContent = React.forwardRef<
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
const PopoverClose = PopoverPrimitive.Close;
export { Popover, PopoverTrigger, PopoverContent, PopoverClose };
+1 -1
View File
@@ -74,7 +74,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
"sticky top-0 z-10 border-b bg-background px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
"sticky top-0 z-10 rounded-t-md border-b bg-background px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
+60
View File
@@ -0,0 +1,60 @@
"use client";
import * as React from "react";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import { type VariantProps } from "class-variance-authority";
import { cn } from "@/src/utils/tailwind";
import { toggleVariants } from "@/src/components/ui/toggle";
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
});
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
));
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext);
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className,
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
);
});
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
export { ToggleGroup, ToggleGroupItem };
+1 -1
View File
@@ -1 +1 @@
export const VERSION = "v2.46.0";
export const VERSION = "v2.49.2";
@@ -11,7 +11,7 @@ import {
PopoverTrigger,
} from "@/src/components/ui/popover";
import { useState } from "react";
import { Trash2 } from "lucide-react";
import { Trash } from "lucide-react";
import {
Tabs,
TabsContent,
@@ -156,7 +156,7 @@ export function DeactivateConfig({
disabled={!hasAccess || config?.status !== "ACTIVE"}
loading={isLoading}
>
<Trash2 className="h-5 w-5" />
<Trash className="h-5 w-5" />
</Button>
</PopoverTrigger>
<PopoverContent>
@@ -1,24 +1,24 @@
import { Terminal } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { z } from "zod";
import { createEmptyMessage } from "@/src/components/ChatMessages/utils/createEmptyMessage";
import { Button } from "@/src/components/ui/button";
import usePlaygroundCache from "@/src/ee/features/playground/page/hooks/usePlaygroundCache";
import { type PlaygroundCache } from "@/src/ee/features/playground/page/types";
import { getIsCloudEnvironment } from "@/src/ee/utils/getIsCloudEnvironment";
import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture";
import { PromptType } from "@/src/features/prompts/server/utils/validation";
import useProjectIdFromURL from "@/src/hooks/useProjectIdFromURL";
import {
ChatMessageRole,
supportedModels as playgroundSupportedModels,
type Observation,
type Prompt,
supportedModels as playgroundSupportedModels,
type UIModelParams,
ZodModelConfig,
} from "@langfuse/shared";
import { useEffect, useState } from "react";
import { PromptType } from "@/src/features/prompts/server/utils/validation";
import { ChatMessageListSchema } from "@/src/features/prompts/components/NewPromptForm/validation";
import { createEmptyMessage } from "@/src/components/ChatMessages/utils/createEmptyMessage";
import { getIsCloudEnvironment } from "@/src/ee/utils/getIsCloudEnvironment";
type JumpToPlaygroundButtonProps = (
| {
@@ -76,9 +76,19 @@ export const JumpToPlaygroundButton: React.FC<JumpToPlaygroundButtonProps> = (
);
};
const ParsedChatMessageListSchema = z.array(
z.object({
role: z.nativeEnum(ChatMessageRole),
content: z.union([
z.string(),
z.any().transform((v) => JSON.stringify(v, null, 2)),
]),
}),
);
const parsePrompt = (prompt: Prompt): PlaygroundCache => {
if (prompt.type === PromptType.Chat) {
const parsedMessages = ChatMessageListSchema.safeParse(prompt.prompt);
const parsedMessages = ParsedChatMessageListSchema.safeParse(prompt.prompt);
return parsedMessages.success ? { messages: parsedMessages.data } : null;
} else {
@@ -109,14 +119,16 @@ const parseGeneration = (generation: Observation): PlaygroundCache => {
}
if (typeof input === "object") {
const parsedMessages = ChatMessageListSchema.safeParse(input);
const parsedMessages = ParsedChatMessageListSchema.safeParse(input);
if (parsedMessages.success)
return { messages: parsedMessages.data, modelParams };
}
if (typeof input === "object" && "messages" in input) {
const parsedMessages = ChatMessageListSchema.safeParse(input["messages"]);
const parsedMessages = ParsedChatMessageListSchema.safeParse(
input["messages"],
);
if (parsedMessages.success)
return { messages: parsedMessages.data, modelParams };
@@ -1,5 +1,5 @@
import { type ChangeEvent } from "react";
import { CheckCircle2, Circle, Trash2Icon } from "lucide-react";
import { CheckCircle2, Circle, TrashIcon } from "lucide-react";
import { Button } from "@/src/components/ui/button";
import { Textarea } from "@/src/components/ui/textarea";
import { type PromptVariable } from "@langfuse/shared";
@@ -46,7 +46,7 @@ export const PromptVariableComponent: React.FC<{
onClick={handleDeleteVariable}
className="p-0"
>
{!isUsed && <Trash2Icon size={16} />}
{!isUsed && <TrashIcon size={16} />}
</Button>
</div>
<Textarea
@@ -49,8 +49,7 @@ export const SaveToPromptButton: React.FC = () => {
)
.data?.prompts.filter((prompt) => prompt.type === PromptType.Chat)
.map((prompt) => ({
label:
prompt.name.slice(0, 20) + (prompt.name.length > 25 ? "..." : ""),
label: prompt.name,
value: prompt.id,
})) ?? [];
@@ -126,7 +125,9 @@ export const SaveToPromptButton: React.FC = () => {
: "opacity-0",
)}
/>
{promptName.label}
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
{promptName.label}
</span>
</CommandItem>
))}
</CommandList>
@@ -1,4 +1,4 @@
import { noHtmlCheck } from "@/src/utils/zod";
import { noHtmlCheck } from "@langfuse/shared";
import * as z from "zod";
export const projectNameSchema = z.object({
+1 -1
View File
@@ -1,4 +1,4 @@
import { noHtmlCheck } from "@/src/utils/zod";
import { noHtmlCheck } from "@langfuse/shared";
import * as z from "zod";
export const signupSchema = z.object({
@@ -12,7 +12,7 @@ import { useEffect } from "react";
import { usdFormatter } from "../../../utils/numbers";
import { DataTableToolbar } from "@/src/components/table/data-table-toolbar";
import useColumnVisibility from "@/src/features/column-visibility/hooks/useColumnVisibility";
import { type Prisma } from "@langfuse/shared";
import { ScoreDataType, type Prisma } from "@langfuse/shared";
import { useRowHeightLocalStorage } from "@/src/components/table/data-table-row-height-switch";
import { IOTableCell } from "@/src/components/ui/CodeJsonViewer";
@@ -120,6 +120,7 @@ export function DatasetRunsTable(props: {
scores={Object.entries(scores).map(([k, v]) => ({
name: k,
value: v,
dataType: ScoreDataType.NUMERIC, // numeric and boolean values treated as numeric
}))}
variant="headings"
/>
@@ -12,7 +12,7 @@ import {
import { throwIfNoAccess } from "@/src/features/rbac/utils/checkAccess";
import { auditLog } from "@/src/features/audit-logs/auditLog";
import { DB } from "@/src/server/db";
import { paginationZod } from "@/src/utils/zod";
import { paginationZod } from "@langfuse/shared";
export const datasetRouter = createTRPCRouter({
allDatasetMeta: protectedProjectProcedure
@@ -137,50 +137,6 @@ export const datasetRouter = createTRPCRouter({
}
>
>(Prisma.sql`
WITH avg_scores_by_run_id AS (
SELECT
ri.dataset_run_id run_id,
s.name score_name,
AVG(s.value) AS average_score_value
FROM
dataset_run_items ri
JOIN scores s
ON s.trace_id = ri.trace_id
AND (ri.observation_id IS NULL OR s.observation_id = ri.observation_id) -- only include scores that are linked to the observation if observation is linked
AND s.project_id = ${input.projectId}
JOIN traces t ON t.id = s.trace_id
WHERE t.project_id = ${input.projectId}
GROUP BY
ri.dataset_run_id,
s.name
ORDER BY
1,
2
),
json_avg_scores_by_run_id AS (
SELECT
run_id,
jsonb_object_agg(score_name,
average_score_value) AS scores
FROM
avg_scores_by_run_id
GROUP BY
run_id
ORDER BY
run_id
),
latency_and_total_cost_by_run_id AS (
SELECT
ri.dataset_run_id run_id,
AVG(CASE WHEN o.end_time IS NULL THEN NULL ELSE (EXTRACT(EPOCH FROM o."end_time") - EXTRACT(EPOCH FROM o."start_time"))::double precision END) AS "avgLatency",
AVG(COALESCE(o.calculated_total_cost, 0)) AS "avgTotalCost"
FROM
dataset_run_items ri
JOIN observations_view o ON o.id = ri.observation_id
WHERE o.project_id = ${input.projectId}
group by 1
)
SELECT
runs.id,
runs.name,
@@ -188,28 +144,53 @@ export const datasetRouter = createTRPCRouter({
runs.metadata,
runs.created_at "createdAt",
runs.updated_at "updatedAt",
COALESCE(avg_scores.scores, '[]'::jsonb) scores,
COALESCE(avg_scores.scores, '{}') scores,
COALESCE(latency_and_total_cost."avgLatency", 0) "avgLatency",
COALESCE(latency_and_total_cost."avgTotalCost", 0) "avgTotalCost",
count(DISTINCT ri.id)::int "countRunItems"
COALESCE(run_items_count.count, 0)::int "countRunItems"
FROM
dataset_runs runs
JOIN datasets ON datasets.id = runs.dataset_id
LEFT JOIN dataset_run_items ri ON ri.dataset_run_id = runs.id
LEFT JOIN json_avg_scores_by_run_id avg_scores ON avg_scores.run_id = runs.id
LEFT JOIN latency_and_total_cost_by_run_id latency_and_total_cost ON latency_and_total_cost.run_id = runs.id
WHERE runs.dataset_id = ${input.datasetId}
LEFT JOIN LATERAL (
SELECT
jsonb_object_agg(s.name, s.avg_value) AS scores
FROM (
SELECT
s.name,
AVG(s.value) AS avg_value
FROM
dataset_run_items ri
JOIN scores s
ON s.trace_id = ri.trace_id
AND (ri.observation_id IS NULL OR s.observation_id = ri.observation_id)
AND s.project_id = ${input.projectId}
JOIN traces t ON t.id = s.trace_id
WHERE
t.project_id = ${input.projectId}
AND s.data_type != 'CATEGORICAL'
AND ri.dataset_run_id = runs.id
GROUP BY s.name
) s
) avg_scores ON true
LEFT JOIN LATERAL (
SELECT
AVG(o.latency) AS "avgLatency",
AVG(COALESCE(o.calculated_total_cost, 0)) AS "avgTotalCost"
FROM
dataset_run_items ri
JOIN observations_view o ON o.id = ri.observation_id
WHERE
o.project_id = ${input.projectId}
AND ri.dataset_run_id = runs.id
) latency_and_total_cost ON true
LEFT JOIN LATERAL (
SELECT count(*) as count
FROM dataset_run_items ri
WHERE ri.dataset_run_id = runs.id
) run_items_count ON true
WHERE
runs.dataset_id = ${input.datasetId}
AND datasets.project_id = ${input.projectId}
GROUP BY
1,
2,
3,
4,
5,
6,
7,
8,
9
ORDER BY
runs.created_at DESC
LIMIT ${input.limit}
@@ -549,19 +530,6 @@ export const datasetRouter = createTRPCRouter({
},
},
},
include: {
datasetItem: true,
observation: {
select: {
id: true,
},
},
trace: {
select: {
id: true,
},
},
},
orderBy: {
createdAt: "desc",
},
@@ -618,18 +586,37 @@ export const datasetRouter = createTRPCRouter({
},
});
const traces = await ctx.prisma.traceView.findMany({
where: {
id: {
in: runItems.map((ri) => ri.traceId).filter(Boolean) as string[],
},
projectId: ctx.session.projectId,
},
select: {
id: true,
duration: true,
},
});
// Directly access 'traces' table and calculate duration via lateral join
// Previously used 'traces_view' was not performant enough
const traceIdsSQL = Prisma.sql`ARRAY[${Prisma.join(runItems.map((ri) => ri.traceId))}]`;
const traces = await ctx.prisma.$queryRaw<
{
id: string;
duration: number;
}[]
>(
Prisma.sql`
SELECT
t.id,
o.duration
FROM
traces t
LEFT JOIN LATERAL (
SELECT
EXTRACT(epoch FROM COALESCE(max(o1.end_time), max(o1.start_time)))::double precision - EXTRACT(epoch FROM min(o1.start_time))::double precision AS duration
FROM
observations o1
WHERE
o1.project_id = t.project_id
AND o1.trace_id = t.id
GROUP BY
o1.project_id,
o1.trace_id) o ON TRUE
WHERE
t.project_id = ${input.projectId}
AND t.id = ANY(${traceIdsSQL})
`,
);
const items = runItems.map((ri) => {
return {
@@ -25,7 +25,7 @@ import {
filterOperators,
singleFilter,
} from "@langfuse/shared";
import { NonEmptyString } from "@/src/utils/zod";
import { NonEmptyString } from "@langfuse/shared";
import { cn } from "@/src/utils/tailwind";
import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture";
@@ -104,7 +104,6 @@ export function MultiSelect({
selectedValues.add(option.value);
}
const filterValues = Array.from(selectedValues);
onValueChange(filterValues.length ? filterValues : []);
}}
>
@@ -118,9 +117,9 @@ export function MultiSelect({
>
<Check className={cn("h-4 w-4")} />
</div>
<span>{option.value}</span>
<span className="overflow-x-scroll">{option.value}</span>
{option.count !== undefined ? (
<span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
<span className="ml-auto flex h-4 w-4 items-center justify-center pl-1 font-mono text-xs">
{option.count}
</span>
) : null}

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