Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b77e5835b | ||
|
|
657bb63c3e | ||
|
|
b101e7c558 | ||
|
|
bac6b13145 | ||
|
|
6817cdab0a | ||
|
|
2d4708921c | ||
|
|
756b2f7501 | ||
|
|
2a685edbfa | ||
|
|
c1c22a9b9b | ||
|
|
ecd3afb4c9 | ||
|
|
362902a69c | ||
|
|
ab14bac8df | ||
|
|
3e92e6b74d | ||
|
|
54f3c224a7 | ||
|
|
e39dda87f9 | ||
|
|
0b8ad96b18 | ||
|
|
a4ba66f329 | ||
|
|
9c473be745 | ||
|
|
4da0b9874a | ||
|
|
8fd6951a19 | ||
|
|
b30734f43c | ||
|
|
293215d1d4 | ||
|
|
ead31c8ca4 | ||
|
|
870336a5ed | ||
|
|
40792d6587 | ||
|
|
820de6f30f | ||
|
|
39fb6d99d4 | ||
|
|
c156a961a2 | ||
|
|
8907056b39 | ||
|
|
7ad6749058 | ||
|
|
2e40330df8 | ||
|
|
3f36fc1191 | ||
|
|
82d7cb9769 | ||
|
|
9bc75b5081 | ||
|
|
3b4811599d | ||
|
|
d6eee1687e | ||
|
|
4b36ff8752 | ||
|
|
64827a0c9d | ||
|
|
fc84da9508 | ||
|
|
3ca321d9f8 | ||
|
|
66df08e232 | ||
|
|
af3cf6d860 | ||
|
|
0edecde6e3 | ||
|
|
5a2fef1fe9 | ||
|
|
28bada800d | ||
|
|
663d67cbd6 | ||
|
|
bd651e81b7 | ||
|
|
f1547dab58 | ||
|
|
34b470f1fe | ||
|
|
040b720dbf | ||
|
|
d56e06d6c9 | ||
|
|
08af244179 | ||
|
|
c821d06099 | ||
|
|
7ed5f71154 | ||
|
|
8c44314f56 | ||
|
|
a3b407ac92 | ||
|
|
b043cf2600 | ||
|
|
d7f75a5e62 | ||
|
|
9951364ff0 | ||
|
|
03cf7bccfc | ||
|
|
4e737ea6cf | ||
|
|
d604ce3ceb | ||
|
|
2c2daf6bb4 | ||
|
|
8ebafda8a4 | ||
|
|
af7384b549 | ||
|
|
9768c2d171 | ||
|
|
f5aec02277 | ||
|
|
b9cf01dd28 | ||
|
|
7c42cf4357 | ||
|
|
72f4b88d15 | ||
|
|
99ce7c5093 | ||
|
|
fcb7b82ba4 | ||
|
|
ac5017c9ae | ||
|
|
00ffddad0f | ||
|
|
28bf40769e | ||
|
|
b65b0e25fc | ||
|
|
62f860d9ef | ||
|
|
3989480a94 | ||
|
|
4a6afddbed | ||
|
|
4c6e8964e3 | ||
|
|
cb900c61f8 | ||
|
|
9bc745d647 | ||
|
|
1b6e9e1c8b | ||
|
|
ce21cf34ae | ||
|
|
bf063a72a0 | ||
|
|
5c2ac78eca | ||
|
|
c6dadda96f | ||
|
|
f831d1ac21 | ||
|
|
1ca4ec7e28 | ||
|
|
a9ca40aa81 | ||
|
|
8b0588a786 | ||
|
|
cc79604302 | ||
|
|
f12532c0a2 | ||
|
|
1c1519555a | ||
|
|
8954cf0c82 | ||
|
|
849428abee | ||
|
|
c014612cca | ||
|
|
803057b5eb | ||
|
|
2152447932 | ||
|
|
70e0b036e5 | ||
|
|
bef666a694 | ||
|
|
601a22d42c | ||
|
|
5fdde72d64 | ||
|
|
f9fc778ee7 | ||
|
|
975ef81bb5 | ||
|
|
28e61fae0b | ||
|
|
0534ccacb2 | ||
|
|
0c5089fd2b | ||
|
|
37c4c1422c | ||
|
|
014da40fbf | ||
|
|
2317334bff | ||
|
|
9ed449288b | ||
|
|
3b4800c37a | ||
|
|
b2ffd05ff8 | ||
|
|
e4594db953 | ||
|
|
4b733c4da8 | ||
|
|
378ac1c1a4 | ||
|
|
9233c7c2d2 | ||
|
|
c2170c0c8d | ||
|
|
7faf0e009f | ||
|
|
86318ab50b | ||
|
|
48d280433a | ||
|
|
45f29eaad8 | ||
|
|
2bca2899f4 | ||
|
|
af6d0f0086 | ||
|
|
3802b1849d | ||
|
|
19d2ee06e9 | ||
|
|
b670abc6bc | ||
|
|
9395e94670 | ||
|
|
684e52e11f | ||
|
|
4048e807af | ||
|
|
0b173f47f7 | ||
|
|
15cad50991 | ||
|
|
747bfae721 | ||
|
|
a01ec32715 | ||
|
|
780fd577c4 | ||
|
|
cd33442168 | ||
|
|
6582dbc645 | ||
|
|
055eb06a4f | ||
|
|
daeaf0a986 | ||
|
|
f199dcb61c | ||
|
|
0ec0cca442 | ||
|
|
f4c75b896b | ||
|
|
16c76b2722 | ||
|
|
64f6051048 | ||
|
|
96dfd86b51 | ||
|
|
b1aaf57ad5 | ||
|
|
fae508d820 | ||
|
|
a6e581d349 | ||
|
|
9e77738a2f | ||
|
|
d15c0a27f5 | ||
|
|
a8626841b5 | ||
|
|
5945a93b7d | ||
|
|
9c447de584 | ||
|
|
48df0e04de | ||
|
|
0897ac753b | ||
|
|
d00c63100e | ||
|
|
e8970bc11c | ||
|
|
eecb253093 | ||
|
|
4e4fbb5101 | ||
|
|
bb4823aa27 | ||
|
|
174d96c100 | ||
|
|
8e59b544c9 | ||
|
|
cffd84f0e7 | ||
|
|
845faee385 | ||
|
|
e45c4af30f | ||
|
|
616544a48c | ||
|
|
75d64b38b3 | ||
|
|
65cdd75282 | ||
|
|
2744028f4c | ||
|
|
3c02c54de2 | ||
|
|
974d2c1007 | ||
|
|
008045c771 | ||
|
|
c6790c6c0e | ||
|
|
909445ce5b | ||
|
|
f4ed4a2635 | ||
|
|
d6206b5269 | ||
|
|
62fa43aa78 | ||
|
|
3c9239a4ab | ||
|
|
476cddb4e1 | ||
|
|
a6c1678da8 | ||
|
|
129805ebde | ||
|
|
59eafb42c3 | ||
|
|
e4695ac717 | ||
|
|
eebd6ea0e9 | ||
|
|
b4a8b08d3d | ||
|
|
ed44b663ff | ||
|
|
720ebe861e | ||
|
|
9a6f8fd75d | ||
|
|
f0da1cfa30 | ||
|
|
0c9ebb3da7 | ||
|
|
3d16d1ac57 | ||
|
|
fa462a8bc4 | ||
|
|
695b65fe0d | ||
|
|
955388e846 | ||
|
|
c6864ed8d9 | ||
|
|
917516fa91 | ||
|
|
19017a6709 | ||
|
|
ca5e6569f9 | ||
|
|
101942b98b | ||
|
|
1188a21b02 | ||
|
|
ec72dd840d | ||
|
|
c2d800ff0e | ||
|
|
dcf159ef82 | ||
|
|
998984b9ed | ||
|
|
022b234e57 | ||
|
|
b8777aa5d1 | ||
|
|
2e84feeab7 | ||
|
|
a508485f88 | ||
|
|
d8bb283a44 | ||
|
|
75ec05e206 | ||
|
|
19f710abe2 | ||
|
|
ad2d6bee66 | ||
|
|
40d0acae5f | ||
|
|
4d387cc35c | ||
|
|
690885c900 | ||
|
|
ef2baa94be | ||
|
|
db6b442832 | ||
|
|
8231ebae4a | ||
|
|
326e8d3189 | ||
|
|
d2001d110d | ||
|
|
3f81c6b9d5 | ||
|
|
991f837ee5 | ||
|
|
062a7f27b8 | ||
|
|
924b326db6 | ||
|
|
2780e66c13 | ||
|
|
c2f51b8d2d | ||
|
|
3a3021a176 | ||
|
|
5dd5a39f90 | ||
|
|
9ee4544e1d | ||
|
|
136f8a3e83 | ||
|
|
eecf0d680b | ||
|
|
d50dbed797 | ||
|
|
bb1dbdf824 | ||
|
|
0991d44525 | ||
|
|
449c9360ac | ||
|
|
cc4a88d6da | ||
|
|
11b2411a5b | ||
|
|
d8a5ebc42e | ||
|
|
9042b005b8 | ||
|
|
7b70b5b678 | ||
|
|
c3ce043d47 | ||
|
|
8c5737ebea | ||
|
|
cb928bf3fc | ||
|
|
dc8a997324 | ||
|
|
31da4bc5a5 | ||
|
|
c0189eecad | ||
|
|
a3f85e75dc | ||
|
|
88ee2f3823 | ||
|
|
0b485ad5d8 | ||
|
|
4e95550411 | ||
|
|
ba9e26181b | ||
|
|
289a7210ee | ||
|
|
fe0c4dc98d | ||
|
|
61c2ec268a | ||
|
|
1df2781b59 | ||
|
|
5478e870b3 | ||
|
|
80c298fa1e | ||
|
|
330cca6978 | ||
|
|
286b78a1fe | ||
|
|
f0c37c8309 | ||
|
|
326b27cdda | ||
|
|
886e352c39 | ||
|
|
1cd76a49f1 | ||
|
|
0639b78468 | ||
|
|
2d6e51e219 | ||
|
|
1ed6358738 | ||
|
|
4fc339dbac | ||
|
|
1e64d97b75 | ||
|
|
4d06482d78 | ||
|
|
603e5ca7c9 | ||
|
|
61c6560f24 | ||
|
|
6bff022a88 | ||
|
|
1f7cac2dd8 | ||
|
|
49bccb8fa0 | ||
|
|
fea8a3d8d1 | ||
|
|
08fcdb68be | ||
|
|
f18a120bd8 | ||
|
|
2b3ca85344 | ||
|
|
8b408858c4 | ||
|
|
89ad92c0ff | ||
|
|
16e1d19917 | ||
|
|
18d5ba1bfa | ||
|
|
95d6b9fe13 | ||
|
|
23183a71c1 | ||
|
|
5eb0a275a5 | ||
|
|
4c5366fa20 | ||
|
|
4ebaf0a24a | ||
|
|
b98b90e635 | ||
|
|
36f11a645f | ||
|
|
3c8b26aa79 | ||
|
|
86840e4119 | ||
|
|
4b412fb6ca | ||
|
|
271f75aa17 | ||
|
|
669ad4cb92 | ||
|
|
9b237d4041 | ||
|
|
15b4ca01ec | ||
|
|
196b5319bf | ||
|
|
d88e0456b8 | ||
|
|
78463a9ce3 | ||
|
|
0bd7219eba | ||
|
|
a307270d8c | ||
|
|
f1555190c1 | ||
|
|
56c77e0de8 | ||
|
|
48cbbb694f | ||
|
|
168dfd2caa | ||
|
|
ae66dd9e6f | ||
|
|
d081aab38f | ||
|
|
a86f0ab263 | ||
|
|
738df01ab7 | ||
|
|
549eac6fd3 | ||
|
|
6d125ec274 | ||
|
|
4f54577f83 | ||
|
|
1ec1bdf849 | ||
|
|
ed5a53b10e | ||
|
|
5b1d9b1dd1 | ||
|
|
adf4508ffd | ||
|
|
45f8b2e97b | ||
|
|
39c49b65a4 | ||
|
|
d905623cb3 | ||
|
|
a87a3505b8 | ||
|
|
591de20a72 | ||
|
|
33d3ab80cb | ||
|
|
6b9773caec | ||
|
|
c90b477d40 | ||
|
|
0f448ccdde | ||
|
|
74f93aad79 | ||
|
|
9650e87f64 | ||
|
|
c5fc30a9d0 | ||
|
|
4a26f47971 | ||
|
|
e3a9e61d07 | ||
|
|
a12c2a3a97 | ||
|
|
80681e08af | ||
|
|
4e97494390 | ||
|
|
346e5fe7b6 | ||
|
|
16482f588a | ||
|
|
f047e21e13 | ||
|
|
534e832c13 | ||
|
|
5dbfec24f0 | ||
|
|
700705be84 |
@@ -0,0 +1,6 @@
|
||||
---
|
||||
description: How to manage authorization and RBAC within full-stack langfuse features
|
||||
globs: web/
|
||||
alwaysApply: true
|
||||
---
|
||||
See [README.md](mdc:web/src/features/rbac/README.md) for details
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
description: How to enforce entitlements across full-stack features
|
||||
globs: web/**
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
Check [README.md](mdc:web/src/features/entitlements/README.md) to learn more about entitlements
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
description: How we build new full-stack features including frontend APIs, file structure, and components
|
||||
globs: web/**
|
||||
alwaysApply: true
|
||||
---
|
||||
## Framework
|
||||
|
||||
- Next.js
|
||||
- Pages router
|
||||
|
||||
## File Structure
|
||||
|
||||
- We generally put all code related to a net-new feature into a folder within web/src/features.
|
||||
- Checkout other features to learn about the common structure.
|
||||
|
||||
## API for frontend features
|
||||
|
||||
- We use TRPC.io to power full-stack features of the Langfuse Frontend
|
||||
- Entry point for all trpc routes: [root.ts](mdc:web/src/server/api/root.ts)
|
||||
- Authentication, see [authorization-and-rbac.mdc](mdc:.cursor/rules/authorization-and-rbac.mdc)
|
||||
- Entitlements, see [entitlements.mdc](mdc:.cursor/rules/entitlements.mdc)
|
||||
|
||||
## Components
|
||||
|
||||
- We use Shadcn/ui
|
||||
- Components in `@/src/components/ui`
|
||||
- If a component is not installed yet, ask the user to install it for you
|
||||
- When creating new custom components that generalize, we add them with a generalizable naming to `@/src/components`
|
||||
|
||||
## Styling
|
||||
|
||||
- We use Tailwind CSS
|
||||
- We use a standard color palette which automatically handles light/dark mode, see [globals.css](mdc:web/src/styles/globals.css)
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
description: How to create new public api routes for Langfuse
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Implementation
|
||||
|
||||
- All public api routes are in /web/src/pages/api/public
|
||||
- New api routes should follow these guidelines:
|
||||
- Use [withMiddlewares.ts](mdc:web/src/features/public-api/server/withMiddlewares.ts) as a wrapper
|
||||
- Define types of api request and response in /web/src/features/public-api/types, use strict() for all zod objects
|
||||
- Add end-to-end test, similar to [datasets-api.servertest.ts](mdc:web/src/__tests__/async/datasets-api.servertest.ts)
|
||||
- Add fern configuration in /fern, learn more about structure here: https://buildwithfern.com/learn/api-definition/fern/overview
|
||||
- Prompt user to regenerate the OpenAPI spec via the fern CLI
|
||||
- Pagination starts at 1, query typing defined in publicApiPaginationZod, return meta in paginationMetaResponseZod
|
||||
+43
-40
@@ -84,6 +84,7 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# AUTH_GITLAB_ISSUER=
|
||||
# AUTH_GITLAB_CLIENT_AUTH_METHOD=
|
||||
# AUTH_GITLAB_CHECKS=
|
||||
# AUTH_GITLAB_URL=
|
||||
# AUTH_AZURE_AD_CLIENT_ID=
|
||||
# AUTH_AZURE_AD_CLIENT_SECRET=
|
||||
# AUTH_AZURE_AD_TENANT_ID=
|
||||
@@ -114,6 +115,11 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# AUTH_KEYCLOAK_ALLOW_ACCOUNT_LINKING=false
|
||||
# AUTH_KEYCLOAK_CLIENT_AUTH_METHOD=
|
||||
# AUTH_KEYCLOAK_CHECKS=
|
||||
# AUTH_WORKOS_CLIENT_ID=
|
||||
# AUTH_WORKOS_CLIENT_SECRET=
|
||||
# AUTH_WORKOS_ALLOW_ACCOUNT_LINKING=false
|
||||
# AUTH_WORKOS_ORGANIZATION_ID=
|
||||
# AUTH_WORKOS_CONNECTION_ID=
|
||||
# AUTH_CUSTOM_CLIENT_ID=
|
||||
# AUTH_CUSTOM_CLIENT_SECRET=
|
||||
# AUTH_CUSTOM_ISSUER=
|
||||
@@ -166,7 +172,32 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# LANGFUSE_INIT_USER_NAME=User Name
|
||||
# LANGFUSE_INIT_USER_PASSWORD=password
|
||||
|
||||
# Redis configuration
|
||||
# REDIS_HOST=
|
||||
# REDIS_PORT=
|
||||
# REDIS_AUTH=
|
||||
# REDIS_CONNECTION_STRING=
|
||||
# REDIS_ENABLE_AUTO_PIPELINING=
|
||||
|
||||
# Cache configuration
|
||||
# LANGFUSE_CACHE_API_KEY_ENABLED=
|
||||
# LANGFUSE_CACHE_API_KEY_TTL_SECONDS=
|
||||
# LANGFUSE_CACHE_PROMPT_ENABLED=
|
||||
# LANGFUSE_CACHE_PROMPT_TTL_SECONDS=
|
||||
|
||||
# Clickhouse configuration
|
||||
# CLICKHOUSE_URL=
|
||||
# CLICKHOUSE_CLUSTER_NAME=default
|
||||
# CLICKHOUSE_DB=default
|
||||
# CLICKHOUSE_USER=
|
||||
# CLICKHOUSE_PASSWORD=
|
||||
# CLICKHOUSE_CLUSTER_ENABLED=true
|
||||
|
||||
# Ingestion configuration
|
||||
# LANGFUSE_INGESTION_QUEUE_DELAY_MS=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_BATCH_SIZE=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_MAX_ATTEMPTS=
|
||||
|
||||
### START Enterprise Edition Configuration
|
||||
|
||||
@@ -188,17 +219,6 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
### END Enterprise Edition Configuration
|
||||
|
||||
|
||||
|
||||
### START Envs to be deprecated in Langfuse v3.0
|
||||
|
||||
# Disable the expensive analytics queries and related features
|
||||
# LANGFUSE_DISABLE_EXPENSIVE_POSTGRES_QUERIES="true"
|
||||
|
||||
### END Envs to be deprecated in Langfuse v3.0
|
||||
|
||||
|
||||
|
||||
|
||||
### START Langfuse Cloud Config
|
||||
# Used for Langfuse Cloud deployments
|
||||
# Not recommended for self-hosted deployments as these are NOT COVERED BY SEMANTIC VERSIONING
|
||||
@@ -230,24 +250,14 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# NEXT_PUBLIC_DEMO_ORG_ID=
|
||||
# NEXT_PUBLIC_DEMO_PROJECT_ID=
|
||||
|
||||
# Crisp chat
|
||||
# NEXT_PUBLIC_CRISP_WEBSITE_ID=
|
||||
# Plain Chat
|
||||
# NEXT_PUBLIC_PLAIN_APP_ID=
|
||||
# PLAIN_AUTHENTICATION_SECRET=
|
||||
|
||||
# Admin API
|
||||
# ADMIN_API_KEY=
|
||||
|
||||
# Redis
|
||||
# REDIS_HOST=
|
||||
# REDIS_PORT=
|
||||
# REDIS_AUTH=
|
||||
# REDIS_CONNECTION_STRING=
|
||||
# REDIS_ENABLE_AUTO_PIPELINING=
|
||||
|
||||
# Cache configuration
|
||||
# LANGFUSE_CACHE_API_KEY_ENABLED=
|
||||
# LANGFUSE_CACHE_API_KEY_TTL_SECONDS=
|
||||
# LANGFUSE_CACHE_PROMPT_ENABLED=
|
||||
# LANGFUSE_CACHE_PROMPT_TTL_SECONDS=
|
||||
# LANGFUSE_CACHE_MODEL_MATCH_ENABLED=
|
||||
# LANGFUSE_CACHE_MODEL_MATCH_TTL_SECONDS=
|
||||
|
||||
# Rate limiting
|
||||
# LANGFUSE_RATE_LIMITS_ENABLED=
|
||||
@@ -256,22 +266,15 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# STRIPE_SECRET_KEY=
|
||||
# STRIPE_WEBHOOK_SIGNING_SECRET=
|
||||
|
||||
# Betterstack Status Page
|
||||
# BETTERSTACK_UPTIME_API_KEY=
|
||||
# BETTERSTACK_UPTIME_STATUS_PAGE_ID=
|
||||
|
||||
### END Langfuse Cloud Config
|
||||
|
||||
|
||||
## START Langfuse V3 Ingestion
|
||||
### START Langfuse CI Config
|
||||
|
||||
# Clickhouse
|
||||
# CLICKHOUSE_MIGRATION_URL=
|
||||
# CLICKHOUSE_URL=
|
||||
# CLICKHOUSE_USER=
|
||||
# CLICKHOUSE_PASSWORD=
|
||||
# CLICKHOUSE_DB=
|
||||
# LANGFUSE_INIT_ORG_CLOUD_PLAN=
|
||||
|
||||
# Ingestion
|
||||
# LANGFUSE_INGESTION_QUEUE_DELAY_MS=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_BATCH_SIZE=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_MAX_ATTEMPTS=
|
||||
|
||||
## END Langfuse V3 Ingestion
|
||||
### END Langfuse CI Config
|
||||
|
||||
@@ -52,10 +52,11 @@ jobs:
|
||||
--build-arg NEXT_PUBLIC_BUILD_ID=${{ github.sha }} \
|
||||
--build-arg NEXT_PUBLIC_POSTHOG_KEY=${{ vars.NEXT_PUBLIC_POSTHOG_KEY }} \
|
||||
--build-arg NEXT_PUBLIC_POSTHOG_HOST=${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} \
|
||||
--build-arg NEXT_PUBLIC_CRISP_WEBSITE_ID=${{ vars.NEXT_PUBLIC_CRISP_WEBSITE_ID }} \
|
||||
--build-arg NEXT_PUBLIC_PLAIN_APP_ID=${{ vars.NEXT_PUBLIC_PLAIN_APP_ID }} \
|
||||
--build-arg SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \
|
||||
--build-arg SENTRY_ORG=${{ vars.SENTRY_ORG }} \
|
||||
--build-arg SENTRY_PROJECT=${{ vars.SENTRY_PROJECT }} \
|
||||
--build-arg NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE=${{ vars.NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE }} \
|
||||
.
|
||||
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
- name: Render AWS ECS Task Definition
|
||||
|
||||
@@ -134,14 +134,14 @@ jobs:
|
||||
grep -v -e '^LANGFUSE_S3_BATCH_EXPORT_ENABLED=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev.example > .env
|
||||
echo "LANGFUSE_INGESTION_QUEUE_DELAY_MS=1" >> .env
|
||||
echo "LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1" >> .env
|
||||
- name: Run + migrate
|
||||
- name: Run dev containers
|
||||
run: |
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
docker compose ps
|
||||
env:
|
||||
POSTGRES_VERSION: ${{ matrix.postgres-version }}
|
||||
- name: Seed DB
|
||||
- name: Migrate DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm --filter=shared ch:up
|
||||
@@ -152,6 +152,7 @@ jobs:
|
||||
env:
|
||||
LANGFUSE_INIT_ORG_ID: "seed-org-id"
|
||||
LANGFUSE_INIT_ORG_NAME: "Seed Org"
|
||||
LANGFUSE_INIT_ORG_CLOUD_PLAN: "Team"
|
||||
LANGFUSE_INIT_PROJECT_ID: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"
|
||||
LANGFUSE_INIT_PROJECT_NAME: "Seed Project"
|
||||
LANGFUSE_INIT_PROJECT_PUBLIC_KEY: "pk-lf-1234567890"
|
||||
@@ -211,14 +212,17 @@ jobs:
|
||||
grep -v -e '^LANGFUSE_S3_BATCH_EXPORT_ENABLED=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev${{ matrix.blob-provider }}.example > .env
|
||||
echo "LANGFUSE_INGESTION_QUEUE_DELAY_MS=1" >> .env
|
||||
echo "LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1" >> .env
|
||||
- name: Run + migrate
|
||||
echo "LANGFUSE_TRACE_DELETE_CONCURRENCY=100" >> .env
|
||||
echo "ADMIN_API_KEY=admin-api-key" >> .env
|
||||
echo "LANGFUSE_EE_LICENSE_KEY=langfuse_ee_test" >> .env
|
||||
- name: Run dev containers
|
||||
run: |
|
||||
docker compose -f docker-compose.dev${{ matrix.blob-provider }}.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
docker compose ps
|
||||
env:
|
||||
POSTGRES_VERSION: ${{ matrix.postgres-version }}
|
||||
- name: Seed DB
|
||||
- name: Migrate DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm --filter=shared ch:up
|
||||
@@ -229,6 +233,7 @@ jobs:
|
||||
env:
|
||||
LANGFUSE_INIT_ORG_ID: "seed-org-id"
|
||||
LANGFUSE_INIT_ORG_NAME: "Seed Org"
|
||||
LANGFUSE_INIT_ORG_CLOUD_PLAN: "Team"
|
||||
LANGFUSE_INIT_PROJECT_ID: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"
|
||||
LANGFUSE_INIT_PROJECT_NAME: "Seed Project"
|
||||
LANGFUSE_INIT_PROJECT_PUBLIC_KEY: "pk-lf-1234567890"
|
||||
@@ -336,14 +341,28 @@ jobs:
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
cp .env.dev.example web/.env
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
docker compose ps
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
- name: Ensure Docker dependencies are healthy
|
||||
run: |
|
||||
if docker-compose ps | grep "(unhealthy)"; then
|
||||
echo "One or more services are unhealthy"
|
||||
exit 1
|
||||
else
|
||||
echo "All services are healthy"
|
||||
fi
|
||||
- name: Seed DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm --filter=shared run ch:up
|
||||
pnpm run db:seed
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale. Please reopen if the issue persists."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
+3
-1
@@ -62,4 +62,6 @@ node_modules
|
||||
**/.next/*
|
||||
**/.turbo/*
|
||||
.yarn
|
||||
.turbo
|
||||
.turbo
|
||||
|
||||
web/test-results/*
|
||||
+15
-138
@@ -27,7 +27,7 @@ The maintainers are available on [Discord](https://langfuse.com/discord) in case
|
||||
|
||||
## Making a change
|
||||
|
||||
_Before making any significant changes, please [open an issue](https://github.com/langfuse/langfuse/issues)._ Discussing your proposed changes ahead of time will make the contribution process smooth for everyone. Large changes that were not discussed in an issue may be rejected.
|
||||
_Before making any significant changes, please [open an issue](https://github.com/langfuse/langfuse/issues)._ Discussing your proposed changes ahead of time will make the contribution process smooth for everyone. Changes that were not discussed in an issue may be rejected.
|
||||
|
||||
Once we've discussed your changes and you've got your code ready, make sure that tests are passing and open your pull request.
|
||||
|
||||
@@ -53,117 +53,10 @@ A good first step is to search for open [issues](https://github.com/langfuse/lan
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
**Langfuse v2**
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph s4["Clients"]
|
||||
subgraph s2["langfuse/langfuse-python"]
|
||||
Python["Python low-level SDK"]
|
||||
Decorator["observe() decorator"] -->|extends| Python
|
||||
OAI["OpenAI drop-in replacement"] -->|extends| Python
|
||||
Llamaindex["LlamaIndex Integration"] -->|extends| Python
|
||||
LCPYTHON["Langchain Python Integration"] -->|extends| Python
|
||||
Langflow -->|uses| LCPYTHON
|
||||
LiteLLM -->|uses| Python
|
||||
end
|
||||
subgraph s3["langfuse/langfuse-js"]
|
||||
JS["JS SDK"]
|
||||
LCJS["Langchain JS Integration"] -->|extends| JS
|
||||
Flowise -->|uses| LCJS
|
||||
end
|
||||
end
|
||||
|
||||
DB[Postgres Database]
|
||||
Redis[Redis]
|
||||
|
||||
subgraph s1["Application (langfuse/langfuse/web)"]
|
||||
API[Public HTTP API]
|
||||
G[TRPC API]
|
||||
I[NextAuth]
|
||||
H[React Frontend]
|
||||
Prisma[Prisma ORM]
|
||||
H --> G
|
||||
H --> I
|
||||
G --> I
|
||||
G --- Prisma
|
||||
API --- Prisma
|
||||
I --- Prisma
|
||||
end
|
||||
|
||||
Prisma --- DB
|
||||
JS --- API
|
||||
Python --- API
|
||||
```
|
||||
|
||||
**Langfuse v3 (work in progress, not released yet)**
|
||||
|
||||
> [!NOTE]
|
||||
> Infrastructure will change in Langfuse version 3.0. More in the [GitHub Discussions](https://github.com/orgs/langfuse/discussions/1902).
|
||||
> `langfuse/langfuse/worker` is under active development and not recommended for production use in Langfuse 2.x.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph s4["Clients"]
|
||||
subgraph s2["langfuse/langfuse-python"]
|
||||
Python["Python low-level SDK"]
|
||||
Decorator["observe() decorator"] -->|extends| Python
|
||||
OAI["OpenAI drop-in replacement"] -->|extends| Python
|
||||
Llamaindex["LlamaIndex Integration"] -->|extends| Python
|
||||
LCPYTHON["Langchain Python Integration"] -->|extends| Python
|
||||
Langflow -->|uses| LCPYTHON
|
||||
LiteLLM -->|uses| Python
|
||||
end
|
||||
subgraph s3["langfuse/langfuse-js"]
|
||||
JS["JS SDK"]
|
||||
LCJS["Langchain JS Integration"] -->|extends| JS
|
||||
Flowise -->|uses| LCJS
|
||||
end
|
||||
end
|
||||
|
||||
subgraph s9 ["VPC (US and EU separated)"]
|
||||
DB[Postgres Database]
|
||||
Redis[Redis Cache/Queue]
|
||||
Clickhouse[Clickhouse Database]
|
||||
|
||||
subgraph s1["Application (langfuse/langfuse/web)"]
|
||||
API[Public HTTP API]
|
||||
G[TRPC API]
|
||||
I[NextAuth]
|
||||
H[React Frontend]
|
||||
ORM
|
||||
H --> G
|
||||
H --> I
|
||||
G --> I
|
||||
G --- ORM
|
||||
API --- ORM
|
||||
I --- ORM
|
||||
end
|
||||
|
||||
subgraph s5["Application (langfuse/langfuse/worker)"]
|
||||
Worker
|
||||
end
|
||||
|
||||
Worker --- DB
|
||||
Worker --- Redis
|
||||
Worker --- Clickhouse
|
||||
|
||||
ORM --- DB
|
||||
ORM --- Redis
|
||||
ORM --- Clickhouse
|
||||
|
||||
|
||||
end
|
||||
|
||||
JS --- API
|
||||
Python --- API
|
||||
```
|
||||
See this [diagram](https://langfuse.com/self-hosting#architecture) for an overview of the architecture.
|
||||
|
||||
### Network Overview
|
||||
|
||||
> [!NOTE]
|
||||
> This will change in Langfuse version 3.0. More in the [GitHub Discussions](https://github.com/orgs/langfuse/discussions/1902).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Browser ---|Web UI & TRPC API| App
|
||||
@@ -209,11 +102,12 @@ Requirements
|
||||
|
||||
**Steps**
|
||||
|
||||
1. Fork the repository and clone it locally
|
||||
2. Run the development database
|
||||
1. Install [golang-migrate](https://github.com/golang-migrate/migrate/tree/master/cmd/migrate#migrate-cli) as CLI
|
||||
2. Fork the repository and clone it locally
|
||||
|
||||
```bash
|
||||
pnpm run infra:dev:up
|
||||
git clone https://github.com/langfuse/langfuse.git
|
||||
cd langfuse
|
||||
```
|
||||
|
||||
3. Create an env file
|
||||
@@ -222,40 +116,23 @@ Requirements
|
||||
cp .env.dev.example .env
|
||||
```
|
||||
|
||||
4. Install dependencies
|
||||
4. Run the entire infrastructure in dev mode
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm run dx
|
||||
```
|
||||
|
||||
5. Run the migrations
|
||||
You will be asked whether you want to reset Postgres and ClickHouse. Confirm both with 'Y' and press enter.
|
||||
|
||||
All database migrations and configs are in the `shared` package.
|
||||
5. Open the web app in your browser to start using Langfuse:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shared run db:migrate
|
||||
- [Sign up page, http://localhost:3000](http://localhost:3000)
|
||||
- [Demo project, http://localhost:3000/project/7a88fb47-b4e2-43b8-a06c-a5ce950dc53a](http://localhost:3000/project/7a88fb47-b4e2-43b8-a06c-a5ce950dc53a)
|
||||
|
||||
# Optional: seed the database
|
||||
# pnpm run db:seed
|
||||
# pnpm run db:seed:examples
|
||||
# pnpm --filter=shared run db:seed:load
|
||||
```
|
||||
6. Log in as a test user:
|
||||
|
||||
6. Start the development server
|
||||
|
||||
```bash
|
||||
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
|
||||
- Username: `demo@langfuse.com`
|
||||
- Password: `password`
|
||||
|
||||
## Monorepo quickstart
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server
|
||||
image: clickhouse/clickhouse-server:24.3
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server
|
||||
image: clickhouse/clickhouse-server:24.3
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
@@ -10,8 +10,8 @@ services:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
ports:
|
||||
- "8123:8123"
|
||||
- "9000:9000"
|
||||
- 127.0.0.1:8123:8123
|
||||
- 127.0.0.1:9000:9000
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
@@ -24,8 +24,8 @@ services:
|
||||
MINIO_ACCESS_KEY: minio
|
||||
MINIO_SECRET_KEY: miniosecret
|
||||
ports:
|
||||
- "9090:9000"
|
||||
- "9091:9001"
|
||||
- 127.0.0.1:9090:9000
|
||||
- 127.0.0.1:9091:9001
|
||||
volumes:
|
||||
- langfuse_minio_data:/data
|
||||
healthcheck:
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
command: >
|
||||
--requirepass ${REDIS_AUTH:-myredissecret}
|
||||
ports:
|
||||
- 6379:6379
|
||||
- 127.0.0.1:6379:6379
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
@@ -57,7 +57,7 @@ services:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
- 127.0.0.1:5432:5432
|
||||
volumes:
|
||||
- langfuse_postgres_data:/var/lib/postgresql/data
|
||||
|
||||
|
||||
+35
-20
@@ -1,3 +1,8 @@
|
||||
# Make sure to update the credential placeholders with your own secrets.
|
||||
# We mark them with # CHANGEME in the file below.
|
||||
# In addition, we recommend to restrict inbound traffic on the host to langfuse-web (port 3000) and minio (port 9090) only.
|
||||
# All other components are bound to localhost (127.0.0.1) to only accept connections from the local machine.
|
||||
# External connections from other machines will not be able to reach these services directly.
|
||||
services:
|
||||
langfuse-worker:
|
||||
image: langfuse/langfuse-worker:3
|
||||
@@ -12,37 +17,46 @@ services:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3030:3030"
|
||||
- 127.0.0.1:3030:3030
|
||||
environment: &langfuse-worker-env
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
|
||||
SALT: "mysalt"
|
||||
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # generate via `openssl rand -hex 32`
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres # CHANGEME
|
||||
SALT: "mysalt" # CHANGEME
|
||||
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # CHANGEME: generate via `openssl rand -hex 32`
|
||||
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
|
||||
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-true}
|
||||
CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL:-clickhouse://clickhouse:9000}
|
||||
CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123}
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME
|
||||
CLICKHOUSE_CLUSTER_ENABLED: ${CLICKHOUSE_CLUSTER_ENABLED:-false}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_PREFIX: ${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: ${LANGFUSE_S3_MEDIA_UPLOAD_BUCKET:-langfuse}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_REGION: ${LANGFUSE_S3_MEDIA_UPLOAD_REGION:-auto}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID:-minio}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://minio:9000}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://localhost:9090}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE:-true}
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: ${LANGFUSE_S3_MEDIA_UPLOAD_PREFIX:-media/}
|
||||
LANGFUSE_S3_BATCH_EXPORT_ENABLED: ${LANGFUSE_S3_BATCH_EXPORT_ENABLED:-false}
|
||||
LANGFUSE_S3_BATCH_EXPORT_BUCKET: ${LANGFUSE_S3_BATCH_EXPORT_BUCKET:-langfuse}
|
||||
LANGFUSE_S3_BATCH_EXPORT_PREFIX: ${LANGFUSE_S3_BATCH_EXPORT_PREFIX:-exports/}
|
||||
LANGFUSE_S3_BATCH_EXPORT_REGION: ${LANGFUSE_S3_BATCH_EXPORT_REGION:-auto}
|
||||
LANGFUSE_S3_BATCH_EXPORT_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_ENDPOINT:-http://minio:9000}
|
||||
LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT:-http://localhost:9090}
|
||||
LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID: ${LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID:-minio}
|
||||
LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY: ${LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME
|
||||
LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE: ${LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE:-true}
|
||||
LANGFUSE_INGESTION_QUEUE_DELAY_MS: ${LANGFUSE_INGESTION_QUEUE_DELAY_MS:-}
|
||||
LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS: ${LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS:-}
|
||||
REDIS_HOST: ${REDIS_HOST:-redis}
|
||||
REDIS_PORT: ${REDIS_PORT:-6379}
|
||||
REDIS_AUTH: ${REDIS_AUTH:-myredissecret}
|
||||
REDIS_AUTH: ${REDIS_AUTH:-myredissecret} # CHANGEME
|
||||
REDIS_TLS_ENABLED: ${REDIS_TLS_ENABLED:-false}
|
||||
REDIS_TLS_CA: ${REDIS_TLS_CA:-/certs/ca.crt}
|
||||
REDIS_TLS_CERT: ${REDIS_TLS_CERT:-/certs/redis.crt}
|
||||
@@ -53,11 +67,11 @@ services:
|
||||
restart: always
|
||||
depends_on: *langfuse-depends-on
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- 3000:3000
|
||||
environment:
|
||||
<<: *langfuse-worker-env
|
||||
NEXTAUTH_URL: http://localhost:3000
|
||||
NEXTAUTH_SECRET: mysecret
|
||||
NEXTAUTH_SECRET: mysecret # CHANGEME
|
||||
LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
|
||||
LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-}
|
||||
LANGFUSE_INIT_PROJECT_ID: ${LANGFUSE_INIT_PROJECT_ID:-}
|
||||
@@ -75,13 +89,13 @@ services:
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
CLICKHOUSE_USER: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse # CHANGEME
|
||||
volumes:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
ports:
|
||||
- "8123:8123"
|
||||
- "9000:9000"
|
||||
- 127.0.0.1:8123:8123
|
||||
- 127.0.0.1:9000:9000
|
||||
healthcheck:
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
|
||||
interval: 5s
|
||||
@@ -97,10 +111,10 @@ services:
|
||||
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
||||
environment:
|
||||
MINIO_ROOT_USER: minio
|
||||
MINIO_ROOT_PASSWORD: miniosecret
|
||||
MINIO_ROOT_PASSWORD: miniosecret # CHANGEME
|
||||
ports:
|
||||
- "9090:9000"
|
||||
- "9091:9001"
|
||||
- 9090:9000
|
||||
- 127.0.0.1:9091:9001
|
||||
volumes:
|
||||
- langfuse_minio_data:/data
|
||||
healthcheck:
|
||||
@@ -113,10 +127,11 @@ services:
|
||||
redis:
|
||||
image: redis:7
|
||||
restart: always
|
||||
# CHANGEME: row below to secure redis password
|
||||
command: >
|
||||
--requirepass ${REDIS_AUTH:-myredissecret}
|
||||
ports:
|
||||
- 6379:6379
|
||||
- 127.0.0.1:6379:6379
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 3s
|
||||
@@ -133,10 +148,10 @@ services:
|
||||
retries: 10
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_PASSWORD: postgres # CHANGEME
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
- 127.0.0.1:5432:5432
|
||||
volumes:
|
||||
- langfuse_postgres_data:/var/lib/postgresql/data
|
||||
|
||||
|
||||
+2
-2
@@ -26,9 +26,9 @@
|
||||
"dependencies": {
|
||||
"@langfuse/shared": "workspace:*",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"axios": "^1.7.7",
|
||||
"axios": "^1.8.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"next": "^14.2.21",
|
||||
"next": "^14.2.26",
|
||||
"next-auth": "^4.24.11",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ types:
|
||||
docs: The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false)
|
||||
observationId: optional<string>
|
||||
comment: optional<string>
|
||||
metadata: optional<unknown>
|
||||
dataType:
|
||||
type: optional<ScoreDataType>
|
||||
docs: When set, must match the score value's type. If not set, will be inferred from the score value or config
|
||||
@@ -76,6 +77,7 @@ types:
|
||||
updatedAt: datetime
|
||||
authorUserId: optional<string>
|
||||
comment: optional<string>
|
||||
metadata: optional<unknown>
|
||||
configId:
|
||||
type: optional<string>
|
||||
docs: Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: langfuse-organizations
|
||||
docs: |
|
||||
## General Notes
|
||||
|
||||
This admin API is only available on self-hosted instances, not on Langfuse Cloud.
|
||||
An administrator must set the `ADMIN_API_KEY` environment variable to use this API.
|
||||
|
||||
## Authentication
|
||||
|
||||
Authenticate with the API by setting the Authorization header to `Bearer $ADMIN_API_KEY` where
|
||||
`$ADMIN_API_KEY` is the API key set via the container environment.
|
||||
|
||||
## Exports
|
||||
|
||||
- OpenAPI spec: https://cloud.langfuse.com/generated/organizations-api/openapi.yml
|
||||
- Postman collection: https://cloud.langfuse.com/generated/organizations-postman/collection.json
|
||||
|
||||
## Looking for the Langfuse API?
|
||||
|
||||
Are you looking for the full Langfuse API?
|
||||
Check out our [API reference](https://api.reference.langfuse.com/#overview).
|
||||
|
||||
error-discrimination:
|
||||
strategy: status-code
|
||||
auth: bearer
|
||||
imports:
|
||||
commons: commons.yml
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.AccessDeniedError
|
||||
- commons.MethodNotAllowedError
|
||||
@@ -0,0 +1,13 @@
|
||||
errors:
|
||||
Error:
|
||||
status-code: 400
|
||||
type: string
|
||||
UnauthorizedError:
|
||||
status-code: 401
|
||||
type: string
|
||||
AccessDeniedError:
|
||||
status-code: 403
|
||||
type: string
|
||||
MethodNotAllowedError:
|
||||
status-code: 405
|
||||
type: string
|
||||
@@ -0,0 +1,170 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/admin
|
||||
endpoints:
|
||||
getAll:
|
||||
docs: Get all organizations
|
||||
method: GET
|
||||
path: /organizations
|
||||
response: Organizations
|
||||
errors:
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
create:
|
||||
docs: Create a new organization
|
||||
method: POST
|
||||
path: /organizations
|
||||
request:
|
||||
name: CreateOrganizationRequest
|
||||
body:
|
||||
properties:
|
||||
name: string
|
||||
metadata:
|
||||
type: optional<map<string, unknown>>
|
||||
docs: Optional metadata for the organization
|
||||
response: Organization
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
getById:
|
||||
docs: Get organization by ID
|
||||
method: GET
|
||||
path: /organizations/{organizationId}
|
||||
path-parameters:
|
||||
organizationId: string
|
||||
response: Organization
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
update:
|
||||
docs: Update an organization
|
||||
method: PUT
|
||||
path: /organizations/{organizationId}
|
||||
path-parameters:
|
||||
organizationId: string
|
||||
request:
|
||||
name: UpdateOrganizationRequest
|
||||
body:
|
||||
properties:
|
||||
name: string
|
||||
metadata:
|
||||
type: optional<map<string, unknown>>
|
||||
docs: Optional metadata for the organization
|
||||
response: Organization
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
delete:
|
||||
docs: Delete an organization
|
||||
method: DELETE
|
||||
path: /organizations/{organizationId}
|
||||
path-parameters:
|
||||
organizationId: string
|
||||
response: DeleteOrganizationResponse
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
# API Key endpoints
|
||||
getApiKeys:
|
||||
docs: Get all API keys for an organization
|
||||
method: GET
|
||||
path: /organizations/{organizationId}/apiKeys
|
||||
path-parameters:
|
||||
organizationId: string
|
||||
response: ApiKeyList
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
createApiKey:
|
||||
docs: Create a new API key for an organization
|
||||
method: POST
|
||||
path: /organizations/{organizationId}/apiKeys
|
||||
path-parameters:
|
||||
organizationId: string
|
||||
request:
|
||||
name: CreateApiKeyRequest
|
||||
body:
|
||||
properties:
|
||||
note:
|
||||
type: optional<string>
|
||||
docs: Optional note for the API key
|
||||
response: ApiKeyResponse
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
deleteApiKey:
|
||||
docs: Delete an API key for an organization
|
||||
method: DELETE
|
||||
path: /organizations/{organizationId}/apiKeys/{apiKeyId}
|
||||
path-parameters:
|
||||
organizationId: string
|
||||
apiKeyId: string
|
||||
response: DeleteOrganizationResponse
|
||||
errors:
|
||||
- commons.Error
|
||||
- commons.UnauthorizedError
|
||||
- commons.MethodNotAllowedError
|
||||
|
||||
types:
|
||||
Organizations:
|
||||
docs: List of organizations
|
||||
properties:
|
||||
organizations: list<Organization>
|
||||
|
||||
Organization:
|
||||
docs: Organization object
|
||||
properties:
|
||||
id: string
|
||||
name: string
|
||||
createdAt: datetime
|
||||
metadata:
|
||||
type: map<string, unknown>
|
||||
docs: Metadata for the organization
|
||||
|
||||
DeleteOrganizationResponse:
|
||||
docs: Response for successful organization deletion
|
||||
properties:
|
||||
success: boolean
|
||||
|
||||
ApiKeyList:
|
||||
docs: List of API keys for an organization
|
||||
properties:
|
||||
apiKeys: list<ApiKeySummary>
|
||||
|
||||
ApiKeySummary:
|
||||
docs: Summary of an API key
|
||||
properties:
|
||||
id: string
|
||||
createdAt: datetime
|
||||
expiresAt: optional<datetime>
|
||||
lastUsedAt: optional<datetime>
|
||||
note: optional<string>
|
||||
publicKey: string
|
||||
displaySecretKey: string
|
||||
|
||||
ApiKeyResponse:
|
||||
docs: Response for API key creation
|
||||
properties:
|
||||
id: string
|
||||
createdAt: datetime
|
||||
publicKey: string
|
||||
secretKey: string
|
||||
displaySecretKey: string
|
||||
note: optional<string>
|
||||
@@ -0,0 +1,17 @@
|
||||
default-group: local
|
||||
groups:
|
||||
local:
|
||||
generators:
|
||||
- name: fernapi/fern-openapi
|
||||
version: 0.1.7
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../web/public/generated/organizations-api
|
||||
config:
|
||||
namespaceExport: Langfuse
|
||||
allowCustomFetcher: true
|
||||
- name: fernapi/fern-postman
|
||||
version: 0.0.45
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../web/public/generated/organizations-postman
|
||||
@@ -0,0 +1,165 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
pagination: ./utils/pagination.yml
|
||||
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public
|
||||
endpoints:
|
||||
listQueues:
|
||||
docs: Get all annotation queues
|
||||
method: GET
|
||||
path: /annotation-queues
|
||||
request:
|
||||
name: GetAnnotationQueuesRequest
|
||||
query-parameters:
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
response: PaginatedAnnotationQueues
|
||||
|
||||
getQueue:
|
||||
docs: Get an annotation queue by ID
|
||||
method: GET
|
||||
path: /annotation-queues/{queueId}
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
response: AnnotationQueue
|
||||
|
||||
listQueueItems:
|
||||
docs: Get items for a specific annotation queue
|
||||
method: GET
|
||||
path: /annotation-queues/{queueId}/items
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
request:
|
||||
name: GetAnnotationQueueItemsRequest
|
||||
query-parameters:
|
||||
status:
|
||||
type: optional<AnnotationQueueStatus>
|
||||
docs: Filter by status
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
response: PaginatedAnnotationQueueItems
|
||||
|
||||
getQueueItem:
|
||||
docs: Get a specific item from an annotation queue
|
||||
method: GET
|
||||
path: /annotation-queues/{queueId}/items/{itemId}
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
itemId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue item
|
||||
response: AnnotationQueueItem
|
||||
|
||||
createQueueItem:
|
||||
docs: Add an item to an annotation queue
|
||||
method: POST
|
||||
path: /annotation-queues/{queueId}/items
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
request: CreateAnnotationQueueItemRequest
|
||||
response: AnnotationQueueItem
|
||||
|
||||
updateQueueItem:
|
||||
docs: Update an annotation queue item
|
||||
method: PATCH
|
||||
path: /annotation-queues/{queueId}/items/{itemId}
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
itemId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue item
|
||||
request: UpdateAnnotationQueueItemRequest
|
||||
response: AnnotationQueueItem
|
||||
|
||||
deleteQueueItem:
|
||||
docs: Remove an item from an annotation queue
|
||||
method: DELETE
|
||||
path: /annotation-queues/{queueId}/items/{itemId}
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
itemId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue item
|
||||
response: DeleteAnnotationQueueItemResponse
|
||||
|
||||
types:
|
||||
AnnotationQueueStatus:
|
||||
enum:
|
||||
- PENDING
|
||||
- COMPLETED
|
||||
|
||||
AnnotationQueueObjectType:
|
||||
enum:
|
||||
- TRACE
|
||||
- OBSERVATION
|
||||
|
||||
AnnotationQueue:
|
||||
properties:
|
||||
id: string
|
||||
name: string
|
||||
description: optional<string>
|
||||
scoreConfigIds: list<string>
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
|
||||
AnnotationQueueItem:
|
||||
properties:
|
||||
id: string
|
||||
queueId: string
|
||||
objectId: string
|
||||
objectType: AnnotationQueueObjectType
|
||||
status: AnnotationQueueStatus
|
||||
completedAt: optional<datetime>
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
|
||||
PaginatedAnnotationQueues:
|
||||
properties:
|
||||
data: list<AnnotationQueue>
|
||||
meta: pagination.MetaResponse
|
||||
|
||||
PaginatedAnnotationQueueItems:
|
||||
properties:
|
||||
data: list<AnnotationQueueItem>
|
||||
meta: pagination.MetaResponse
|
||||
|
||||
CreateAnnotationQueueItemRequest:
|
||||
properties:
|
||||
objectId: string
|
||||
objectType: AnnotationQueueObjectType
|
||||
status:
|
||||
type: optional<AnnotationQueueStatus>
|
||||
docs: Defaults to PENDING for new queue items
|
||||
|
||||
UpdateAnnotationQueueItemRequest:
|
||||
properties:
|
||||
status:
|
||||
type: optional<AnnotationQueueStatus>
|
||||
|
||||
DeleteAnnotationQueueItemResponse:
|
||||
properties:
|
||||
success: boolean
|
||||
message: string
|
||||
@@ -82,6 +82,9 @@ types:
|
||||
id: string
|
||||
createdAt: datetime
|
||||
projectId: string
|
||||
environment:
|
||||
type: optional<string>
|
||||
docs: The environment from which this session originated.
|
||||
SessionWithTraces:
|
||||
extends: Session
|
||||
properties:
|
||||
@@ -249,6 +252,7 @@ types:
|
||||
updatedAt: datetime
|
||||
authorUserId: optional<string>
|
||||
comment: optional<string>
|
||||
metadata: optional<unknown>
|
||||
configId:
|
||||
type: optional<string>
|
||||
docs: Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range
|
||||
|
||||
@@ -37,8 +37,21 @@ service:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
response: PaginatedDatasetItems
|
||||
delete:
|
||||
docs: Delete a dataset item and all its run items. This action is irreversible.
|
||||
method: DELETE
|
||||
path: /dataset-items/{id}
|
||||
path-parameters:
|
||||
id:
|
||||
type: string
|
||||
response: DeleteDatasetItemResponse
|
||||
|
||||
types:
|
||||
DeleteDatasetItemResponse:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
docs: Success message after deletion
|
||||
CreateDatasetItemRequest:
|
||||
properties:
|
||||
datasetName: string
|
||||
|
||||
@@ -41,6 +41,14 @@ service:
|
||||
datasetName: string
|
||||
runName: string
|
||||
response: commons.DatasetRunWithItems
|
||||
deleteRun:
|
||||
method: DELETE
|
||||
docs: Delete a dataset run and all its run items. This action is irreversible.
|
||||
path: /datasets/{datasetName}/runs/{runName}
|
||||
path-parameters:
|
||||
datasetName: string
|
||||
runName: string
|
||||
response: DeleteDatasetRunResponse
|
||||
getRuns:
|
||||
method: GET
|
||||
docs: Get dataset runs
|
||||
@@ -72,3 +80,6 @@ types:
|
||||
properties:
|
||||
data: list<commons.DatasetRun>
|
||||
meta: pagination.MetaResponse
|
||||
DeleteDatasetRunResponse:
|
||||
properties:
|
||||
message: string
|
||||
|
||||
@@ -47,6 +47,7 @@ service:
|
||||
body:
|
||||
id: abcdef-1234-5678-90ab
|
||||
timestamp: "2022-01-01T00:00:00.000Z"
|
||||
environment: "production"
|
||||
name: "My Trace"
|
||||
userId: "1234-5678-90ab-cdef"
|
||||
input: "My input"
|
||||
@@ -74,6 +75,7 @@ service:
|
||||
id: abcdef-1234-5678-90ab
|
||||
traceId: "1234-5678-90ab-cdef"
|
||||
startTime: "2022-01-01T00:00:00.000Z"
|
||||
environment: "test"
|
||||
response:
|
||||
body:
|
||||
successes:
|
||||
@@ -92,6 +94,7 @@ service:
|
||||
traceId: "1234-5678-90ab-cdef"
|
||||
name: "My Score"
|
||||
value: 0.9
|
||||
environment: "default"
|
||||
response:
|
||||
body:
|
||||
successes:
|
||||
@@ -268,6 +271,7 @@ types:
|
||||
docs: The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false)
|
||||
observationId: optional<string>
|
||||
comment: optional<string>
|
||||
metadata: optional<unknown>
|
||||
dataType:
|
||||
type: optional<commons.ScoreDataType>
|
||||
docs: When set, must match the score value's type. If not set, will be inferred from the score value or config
|
||||
@@ -391,16 +395,27 @@ types:
|
||||
successes: list<IngestionSuccess>
|
||||
errors: list<IngestionError>
|
||||
|
||||
OpenAIUsageSchema:
|
||||
OpenAICompletionUsageSchema:
|
||||
docs: OpenAI Usage schema from (Chat-)Completion APIs
|
||||
properties:
|
||||
prompt_tokens: integer
|
||||
completion_tokens: integer
|
||||
total_tokens: integer
|
||||
prompt_tokens_details: optional<map<string, integer>>
|
||||
completion_tokens_details: optional<map<string, integer>>
|
||||
prompt_tokens_details: optional<map<string, optional<integer>>>
|
||||
completion_tokens_details: optional<map<string, optional<integer>>>
|
||||
|
||||
OpenAIResponseUsageSchema:
|
||||
docs: OpenAI Usage schema from Response API
|
||||
properties:
|
||||
input_tokens: integer
|
||||
output_tokens: integer
|
||||
total_tokens: integer
|
||||
input_tokens_details: optional<map<string, optional<integer>>>
|
||||
output_tokens_details: optional<map<string, optional<integer>>>
|
||||
|
||||
UsageDetails:
|
||||
discriminated: false
|
||||
union:
|
||||
- map<string, integer>
|
||||
- OpenAIUsageSchema
|
||||
- OpenAICompletionUsageSchema
|
||||
- OpenAIResponseUsageSchema
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
pagination: ./utils/pagination.yml
|
||||
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public
|
||||
endpoints:
|
||||
getOrganizationMemberships:
|
||||
docs: Get all memberships for the organization associated with the API key (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /organizations/memberships
|
||||
response: MembershipsResponse
|
||||
|
||||
updateOrganizationMembership:
|
||||
docs: Create or update a membership for the organization associated with the API key (requires organization-scoped API key)
|
||||
method: PUT
|
||||
path: /organizations/memberships
|
||||
request: MembershipRequest
|
||||
response: MembershipResponse
|
||||
|
||||
getProjectMemberships:
|
||||
docs: Get all memberships for a specific project (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /projects/{projectId}/memberships
|
||||
path-parameters:
|
||||
projectId: string
|
||||
response: MembershipsResponse
|
||||
|
||||
updateProjectMembership:
|
||||
docs: Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization.
|
||||
method: PUT
|
||||
path: /projects/{projectId}/memberships
|
||||
path-parameters:
|
||||
projectId: string
|
||||
request: MembershipRequest
|
||||
response: MembershipResponse
|
||||
|
||||
types:
|
||||
MembershipRole:
|
||||
enum:
|
||||
- OWNER
|
||||
- ADMIN
|
||||
- MEMBER
|
||||
- VIEWER
|
||||
|
||||
MembershipRequest:
|
||||
properties:
|
||||
userId: string
|
||||
role: MembershipRole
|
||||
|
||||
MembershipResponse:
|
||||
properties:
|
||||
userId: string
|
||||
role: MembershipRole
|
||||
email: string
|
||||
name: string
|
||||
|
||||
MembershipsResponse:
|
||||
properties:
|
||||
memberships: list<MembershipResponse>
|
||||
@@ -29,6 +29,10 @@ service:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for metrics where traces include all of these tags
|
||||
environment:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for metrics where events include any of these environments
|
||||
fromTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Optional filter to only include traces and observations on or after a certain datetime (ISO 8601)
|
||||
|
||||
@@ -33,6 +33,10 @@ service:
|
||||
type: optional<string>
|
||||
traceId: optional<string>
|
||||
parentObservationId: optional<string>
|
||||
environment:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for observations where the environment is one of the provided values.
|
||||
fromStartTime:
|
||||
type: optional<datetime>
|
||||
docs: Retrieve only observations with a start_time or or after this datetime (ISO 8601).
|
||||
|
||||
@@ -11,6 +11,85 @@ service:
|
||||
method: GET
|
||||
path: /projects
|
||||
response: Projects
|
||||
|
||||
create:
|
||||
docs: Create a new project (requires organization-scoped API key)
|
||||
method: POST
|
||||
path: /projects
|
||||
request:
|
||||
name: CreateProjectRequest
|
||||
body:
|
||||
properties:
|
||||
name: string
|
||||
metadata:
|
||||
type: optional<map<string, unknown>>
|
||||
docs: Optional metadata for the project
|
||||
retention:
|
||||
type: integer
|
||||
docs: Number of days to retain data. Must be 0 or at least 7 days. Requires data-retention entitlement for non-zero values. Optional.
|
||||
response: Project
|
||||
|
||||
update:
|
||||
docs: Update a project by ID (requires organization-scoped API key).
|
||||
method: PUT
|
||||
path: /projects/{projectId}
|
||||
path-parameters:
|
||||
projectId: string
|
||||
request:
|
||||
name: UpdateProjectRequest
|
||||
body:
|
||||
properties:
|
||||
name: string
|
||||
metadata:
|
||||
type: optional<map<string, unknown>>
|
||||
docs: Optional metadata for the project
|
||||
retention:
|
||||
type: integer
|
||||
docs: Number of days to retain data. Must be 0 or at least 7 days. Requires data-retention entitlement for non-zero values. Optional.
|
||||
response: Project
|
||||
|
||||
delete:
|
||||
docs: Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously.
|
||||
method: DELETE
|
||||
path: /projects/{projectId}
|
||||
path-parameters:
|
||||
projectId: string
|
||||
response:
|
||||
status-code: 202
|
||||
type: ProjectDeletionResponse
|
||||
|
||||
# API Key endpoints
|
||||
getApiKeys:
|
||||
docs: Get all API keys for a project (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /projects/{projectId}/apiKeys
|
||||
path-parameters:
|
||||
projectId: string
|
||||
response: ApiKeyList
|
||||
|
||||
createApiKey:
|
||||
docs: Create a new API key for a project (requires organization-scoped API key)
|
||||
method: POST
|
||||
path: /projects/{projectId}/apiKeys
|
||||
path-parameters:
|
||||
projectId: string
|
||||
request:
|
||||
name: CreateApiKeyRequest
|
||||
body:
|
||||
properties:
|
||||
note:
|
||||
type: optional<string>
|
||||
docs: Optional note for the API key
|
||||
response: ApiKeyResponse
|
||||
|
||||
deleteApiKey:
|
||||
docs: Delete an API key for a project (requires organization-scoped API key)
|
||||
method: DELETE
|
||||
path: /projects/{projectId}/apiKeys/{apiKeyId}
|
||||
path-parameters:
|
||||
projectId: string
|
||||
apiKeyId: string
|
||||
response: ApiKeyDeletionResponse
|
||||
|
||||
types:
|
||||
Projects:
|
||||
@@ -21,3 +100,45 @@ types:
|
||||
properties:
|
||||
id: string
|
||||
name: string
|
||||
metadata:
|
||||
type: map<string, unknown>
|
||||
docs: Metadata for the project
|
||||
retentionDays:
|
||||
type: integer
|
||||
docs: Number of days to retain data. Null or 0 means no retention. Omitted if no retention is configured.
|
||||
|
||||
ProjectDeletionResponse:
|
||||
properties:
|
||||
success: boolean
|
||||
message: string
|
||||
|
||||
ApiKeyList:
|
||||
docs: List of API keys for a project
|
||||
properties:
|
||||
apiKeys: list<ApiKeySummary>
|
||||
|
||||
ApiKeySummary:
|
||||
docs: Summary of an API key
|
||||
properties:
|
||||
id: string
|
||||
createdAt: datetime
|
||||
expiresAt: optional<datetime>
|
||||
lastUsedAt: optional<datetime>
|
||||
note: optional<string>
|
||||
publicKey: string
|
||||
displaySecretKey: string
|
||||
|
||||
ApiKeyResponse:
|
||||
docs: Response for API key creation
|
||||
properties:
|
||||
id: string
|
||||
createdAt: datetime
|
||||
publicKey: string
|
||||
secretKey: string
|
||||
displaySecretKey: string
|
||||
note: optional<string>
|
||||
|
||||
ApiKeyDeletionResponse:
|
||||
docs: Response for API key deletion
|
||||
properties:
|
||||
success: boolean
|
||||
|
||||
@@ -128,6 +128,9 @@ types:
|
||||
commitMessage:
|
||||
type: optional<string>
|
||||
docs: Commit message for this prompt version.
|
||||
resolutionGraph:
|
||||
type: optional<map<string, unknown>>
|
||||
docs: The dependency resolution graph for the current prompt. Null if prompt has no dependencies.
|
||||
|
||||
ChatMessage:
|
||||
properties:
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public/scim
|
||||
endpoints:
|
||||
getServiceProviderConfig:
|
||||
docs: Get SCIM Service Provider Configuration (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /ServiceProviderConfig
|
||||
response: ServiceProviderConfig
|
||||
|
||||
getResourceTypes:
|
||||
docs: Get SCIM Resource Types (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /ResourceTypes
|
||||
response: ResourceTypesResponse
|
||||
|
||||
getSchemas:
|
||||
docs: Get SCIM Schemas (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /Schemas
|
||||
response: SchemasResponse
|
||||
|
||||
listUsers:
|
||||
docs: List users in the organization (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /Users
|
||||
request:
|
||||
name: ListUsersRequest
|
||||
query-parameters:
|
||||
filter:
|
||||
docs: Filter expression (e.g. userName eq "value")
|
||||
type: optional<string>
|
||||
startIndex:
|
||||
docs: 1-based index of the first result to return (default 1)
|
||||
type: optional<integer>
|
||||
count:
|
||||
docs: Maximum number of results to return (default 100)
|
||||
type: optional<integer>
|
||||
response: ScimUsersListResponse
|
||||
|
||||
createUser:
|
||||
docs: Create a new user in the organization (requires organization-scoped API key)
|
||||
method: POST
|
||||
path: /Users
|
||||
request:
|
||||
name: CreateUserRequest
|
||||
body:
|
||||
properties:
|
||||
userName:
|
||||
docs: User's email address (required)
|
||||
type: string
|
||||
name:
|
||||
docs: User's name information
|
||||
type: ScimName
|
||||
emails:
|
||||
docs: User's email addresses
|
||||
type: optional<list<ScimEmail>>
|
||||
active:
|
||||
docs: Whether the user is active
|
||||
type: optional<boolean>
|
||||
password:
|
||||
docs: Initial password for the user
|
||||
type: optional<string>
|
||||
response: ScimUser
|
||||
|
||||
getUser:
|
||||
docs: Get a specific user by ID (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /Users/{userId}
|
||||
path-parameters:
|
||||
userId: string
|
||||
response: ScimUser
|
||||
|
||||
deleteUser:
|
||||
docs: Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself.
|
||||
method: DELETE
|
||||
path: /Users/{userId}
|
||||
path-parameters:
|
||||
userId: string
|
||||
response: EmptyResponse
|
||||
|
||||
types:
|
||||
ServiceProviderConfig:
|
||||
properties:
|
||||
schemas: list<string>
|
||||
documentationUri: string
|
||||
patch: ScimFeatureSupport
|
||||
bulk: BulkConfig
|
||||
filter: FilterConfig
|
||||
changePassword: ScimFeatureSupport
|
||||
sort: ScimFeatureSupport
|
||||
etag: ScimFeatureSupport
|
||||
authenticationSchemes: list<AuthenticationScheme>
|
||||
meta: ResourceMeta
|
||||
|
||||
ScimFeatureSupport:
|
||||
properties:
|
||||
supported: boolean
|
||||
|
||||
BulkConfig:
|
||||
properties:
|
||||
supported: boolean
|
||||
maxOperations: integer
|
||||
maxPayloadSize: integer
|
||||
|
||||
FilterConfig:
|
||||
properties:
|
||||
supported: boolean
|
||||
maxResults: integer
|
||||
|
||||
ResourceMeta:
|
||||
properties:
|
||||
resourceType: string
|
||||
location: string
|
||||
|
||||
AuthenticationScheme:
|
||||
properties:
|
||||
name: string
|
||||
description: string
|
||||
specUri: string
|
||||
type: string
|
||||
primary: boolean
|
||||
|
||||
ResourceTypesResponse:
|
||||
properties:
|
||||
schemas: list<string>
|
||||
totalResults: integer
|
||||
Resources: list<ResourceType>
|
||||
|
||||
ResourceType:
|
||||
properties:
|
||||
schemas: optional<list<string>>
|
||||
id: string
|
||||
name: string
|
||||
endpoint: string
|
||||
description: string
|
||||
schema: string
|
||||
schemaExtensions: list<SchemaExtension>
|
||||
meta: ResourceMeta
|
||||
|
||||
SchemaExtension:
|
||||
properties:
|
||||
schema: string
|
||||
required: boolean
|
||||
|
||||
SchemasResponse:
|
||||
properties:
|
||||
schemas: list<string>
|
||||
totalResults: integer
|
||||
Resources: list<SchemaResource>
|
||||
|
||||
SchemaResource:
|
||||
properties:
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
attributes: list<unknown>
|
||||
meta: ResourceMeta
|
||||
|
||||
ScimUsersListResponse:
|
||||
properties:
|
||||
schemas: list<string>
|
||||
totalResults: integer
|
||||
startIndex: integer
|
||||
itemsPerPage: integer
|
||||
Resources: list<ScimUser>
|
||||
|
||||
ScimUser:
|
||||
properties:
|
||||
schemas: list<string>
|
||||
id: string
|
||||
userName: string
|
||||
name: ScimName
|
||||
emails: list<ScimEmail>
|
||||
meta: UserMeta
|
||||
|
||||
UserMeta:
|
||||
properties:
|
||||
resourceType: string
|
||||
created: optional<string>
|
||||
lastModified: optional<string>
|
||||
|
||||
ScimName:
|
||||
properties:
|
||||
formatted: optional<string>
|
||||
|
||||
ScimEmail:
|
||||
properties:
|
||||
primary: boolean
|
||||
value: string
|
||||
type: string
|
||||
|
||||
EmptyResponse:
|
||||
docs: Empty response for 204 No Content responses
|
||||
properties: {}
|
||||
@@ -37,6 +37,10 @@ service:
|
||||
toTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Optional filter to only include scores created before a certain datetime (ISO 8601)
|
||||
environment:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for scores where the environment is one of the provided values.
|
||||
source:
|
||||
type: optional<commons.ScoreSource>
|
||||
docs: Retrieve only scores from a specific source.
|
||||
@@ -91,6 +95,10 @@ types:
|
||||
docs: The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false)
|
||||
observationId: optional<string>
|
||||
comment: optional<string>
|
||||
metadata: optional<unknown>
|
||||
environment:
|
||||
type: optional<string>
|
||||
docs: The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'.
|
||||
dataType:
|
||||
type: optional<commons.ScoreDataType>
|
||||
docs: The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric.
|
||||
@@ -113,10 +121,12 @@ types:
|
||||
dataType: "NUMERIC"
|
||||
configId: "9203-4567-89ab-cdef"
|
||||
traceId: "cdef-1234-5678-90ab"
|
||||
environment: "test"
|
||||
- value:
|
||||
name: "toxicity"
|
||||
value: "not toxic"
|
||||
traceId: "cdef-1234-5678-90ab"
|
||||
environment: "production"
|
||||
- value:
|
||||
name: "correctness"
|
||||
value: "partially correct"
|
||||
@@ -147,6 +157,9 @@ types:
|
||||
tags:
|
||||
type: optional<list<string>>
|
||||
docs: A list of tags associated with the trace referenced by score
|
||||
environment:
|
||||
type: optional<string>
|
||||
docs: The environment of the trace referenced by score
|
||||
|
||||
GetScoresResponseDataNumeric:
|
||||
extends: commons.NumericScore
|
||||
|
||||
@@ -25,6 +25,10 @@ service:
|
||||
toTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Optional filter to only include sessions created before a certain datetime (ISO 8601)
|
||||
environment:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for sessions where the environment is one of the provided values.
|
||||
response: PaginatedSessions
|
||||
get:
|
||||
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>`
|
||||
|
||||
@@ -15,6 +15,15 @@ service:
|
||||
type: string
|
||||
docs: The unique langfuse identifier of a trace
|
||||
response: commons.TraceWithFullDetails
|
||||
delete:
|
||||
docs: Delete a specific trace
|
||||
method: DELETE
|
||||
path: /traces/{traceId}
|
||||
path-parameters:
|
||||
traceId:
|
||||
type: string
|
||||
docs: The unique langfuse identifier of the trace to delete
|
||||
response: DeleteTraceResponse
|
||||
list:
|
||||
docs: Get list of traces
|
||||
method: GET
|
||||
@@ -50,13 +59,32 @@ service:
|
||||
release:
|
||||
type: optional<string>
|
||||
docs: Optional filter to only include traces with a certain release.
|
||||
environment:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for traces where the environment is one of the provided values.
|
||||
response: Traces
|
||||
deleteMultiple:
|
||||
docs: Delete multiple traces
|
||||
method: DELETE
|
||||
path: /traces
|
||||
request:
|
||||
name: DeleteTracesRequest
|
||||
body:
|
||||
properties:
|
||||
traceIds:
|
||||
type: list<string>
|
||||
docs: List of trace IDs to delete
|
||||
response: DeleteTraceResponse
|
||||
|
||||
types:
|
||||
Traces:
|
||||
properties:
|
||||
data: list<commons.TraceWithDetails>
|
||||
meta: pagination.MetaResponse
|
||||
DeleteTraceResponse:
|
||||
properties:
|
||||
message: string
|
||||
Sort:
|
||||
properties:
|
||||
id: string
|
||||
|
||||
@@ -19,6 +19,13 @@ groups:
|
||||
pydantic_config:
|
||||
require_optional_fields: false
|
||||
use_str_enums: false
|
||||
# - name: fernapi/fern-java-sdk
|
||||
# version: 2.20.1
|
||||
# output:
|
||||
# location: local-file-system
|
||||
# path: ../../../../langfuse-java/src/main/java/com/langfuse/client/
|
||||
# config:
|
||||
# client-class-name: LangfuseClient
|
||||
- name: fernapi/fern-postman
|
||||
version: 0.0.45
|
||||
output:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"organization": "langfuse",
|
||||
"version": "0.43.7"
|
||||
"version": "0.56.0"
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langfuse",
|
||||
"version": "3.32.1",
|
||||
"version": "3.52.0",
|
||||
"author": "engineering@langfuse.com",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Drop materialized views
|
||||
DROP VIEW IF EXISTS project_environments_traces_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS project_environments_observations_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS project_environments_scores_mv ON CLUSTER default;
|
||||
|
||||
-- Drop the project_environments table
|
||||
DROP TABLE IF EXISTS project_environments ON CLUSTER default;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Create the project_environments table with AggregatingMergeTree engine
|
||||
CREATE TABLE project_environments ON CLUSTER default (
|
||||
`project_id` String,
|
||||
`environments` SimpleAggregateFunction(groupUniqArrayArray, Array(String))
|
||||
) ENGINE = ReplicatedAggregatingMergeTree
|
||||
ORDER BY (project_id);
|
||||
|
||||
-- Create materialized view for traces
|
||||
CREATE MATERIALIZED VIEW project_environments_traces_mv ON CLUSTER default TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM traces
|
||||
GROUP BY project_id;
|
||||
|
||||
-- Create materialized view for observations
|
||||
CREATE MATERIALIZED VIEW project_environments_observations_mv ON CLUSTER default TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM observations
|
||||
GROUP BY project_id;
|
||||
|
||||
-- Create materialized view for scores
|
||||
CREATE MATERIALIZED VIEW project_environments_scores_mv ON CLUSTER default TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM scores
|
||||
GROUP BY project_id;
|
||||
+1
@@ -0,0 +1 @@
|
||||
ALTER TABLE scores ON CLUSTER default DROP COLUMN IF EXISTS metadata;
|
||||
+1
@@ -0,0 +1 @@
|
||||
ALTER TABLE scores ON CLUSTER default ADD COLUMN metadata Map(LowCardinality(String), String) AFTER comment;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
-- Drop materialized views
|
||||
DROP VIEW IF EXISTS project_environments_traces_mv;
|
||||
DROP VIEW IF EXISTS project_environments_observations_mv;
|
||||
DROP VIEW IF EXISTS project_environments_scores_mv;
|
||||
|
||||
-- Drop the project_environments table
|
||||
DROP TABLE IF EXISTS project_environments;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
-- Create the project_environments table with AggregatingMergeTree engine
|
||||
CREATE TABLE project_environments (
|
||||
`project_id` String,
|
||||
`environments` SimpleAggregateFunction(groupUniqArrayArray, Array(String))
|
||||
) ENGINE = AggregatingMergeTree
|
||||
ORDER BY (project_id);
|
||||
|
||||
-- Create materialized view for traces
|
||||
CREATE MATERIALIZED VIEW project_environments_traces_mv TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM traces
|
||||
GROUP BY project_id;
|
||||
|
||||
-- Create materialized view for observations
|
||||
CREATE MATERIALIZED VIEW project_environments_observations_mv TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM observations
|
||||
GROUP BY project_id;
|
||||
|
||||
-- Create materialized view for scores
|
||||
CREATE MATERIALIZED VIEW project_environments_scores_mv TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM scores
|
||||
GROUP BY project_id;
|
||||
+1
@@ -0,0 +1 @@
|
||||
ALTER TABLE scores DROP COLUMN IF EXISTS metadata;
|
||||
+1
@@ -0,0 +1 @@
|
||||
ALTER TABLE scores ADD COLUMN metadata Map(LowCardinality(String), String) AFTER comment;
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ObservationRecordReadType } from "@langfuse/shared/src/server";
|
||||
import { prisma } from "../../src/db";
|
||||
import { redis } from "@langfuse/shared/src/server";
|
||||
import { ObservationRecordReadType, redis } from "../../src/server";
|
||||
import { prepareClickhouse } from "../../scripts/prepareClickhouse";
|
||||
import { createDatasets } from "../../prisma/seed";
|
||||
import { queryClickhouse } from "../../src/server/repositories/clickhouse";
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 64",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 65",
|
||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"db:migrate": "DISABLE_ERD=false dotenv -e ../../.env -- npx prisma migrate dev",
|
||||
"db:push": "DISABLE_ERD=false dotenv -e ../../.env -- npx prisma db push",
|
||||
@@ -47,7 +47,7 @@
|
||||
"ch:down": "bash clickhouse/scripts/down.sh",
|
||||
"ch:drop": "bash clickhouse/scripts/drop.sh",
|
||||
"ch:reset": "pnpm run ch:down && pnpm run ch:up && pnpm run ch:seed",
|
||||
"ch:seed": "dotenv -e ../../.env -- tsx clickhouse/scripts/seed.ts",
|
||||
"ch:seed": "dotenv -e ../../.env -- ts-node -r tsconfig-paths/register -r dotenv/config --compiler-options '{\"module\":\"CommonJS\"}' clickhouse/scripts/seed.ts",
|
||||
"load:setup": "dotenv -e ../../.env -- tsx scripts/load-seed.ts"
|
||||
},
|
||||
"prisma": {
|
||||
@@ -61,6 +61,7 @@
|
||||
"@aws-sdk/s3-request-presigner": "^3.679.0",
|
||||
"@azure/storage-blob": "^12.26.0",
|
||||
"@clickhouse/client": "^1.4.0",
|
||||
"@google-cloud/storage": "^7.15.2",
|
||||
"@langchain/anthropic": "^0.3.12",
|
||||
"@langchain/aws": "^0.1.3",
|
||||
"@langchain/core": "^0.3.37",
|
||||
@@ -72,7 +73,7 @@
|
||||
"@react-email/components": "^0.0.19",
|
||||
"@react-email/render": "^0.0.15",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"axios": "^1.7.7",
|
||||
"axios": "^1.8.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bullmq": "^5.34.10",
|
||||
"dd-trace": "^5.36.0",
|
||||
@@ -84,6 +85,7 @@
|
||||
"langchain": "^0.3.15",
|
||||
"langfuse-langchain": "3.30.3",
|
||||
"lodash": "^4.17.21",
|
||||
"lossless-json": "^4.0.2",
|
||||
"next-auth": "^4.24.11",
|
||||
"nodemailer": "^6.9.15",
|
||||
"prisma-extension-kysely": "^2.1.0",
|
||||
|
||||
@@ -4,6 +4,11 @@ export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
: ColumnType<T, T | undefined, T>;
|
||||
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||
|
||||
export const ApiKeyScope = {
|
||||
ORGANIZATION: "ORGANIZATION",
|
||||
PROJECT: "PROJECT"
|
||||
} as const;
|
||||
export type ApiKeyScope = (typeof ApiKeyScope)[keyof typeof ApiKeyScope];
|
||||
export const Role = {
|
||||
OWNER: "OWNER",
|
||||
ADMIN: "ADMIN",
|
||||
@@ -59,6 +64,11 @@ export const CommentObjectType = {
|
||||
PROMPT: "PROMPT"
|
||||
} as const;
|
||||
export type CommentObjectType = (typeof CommentObjectType)[keyof typeof CommentObjectType];
|
||||
export const AuditLogRecordType = {
|
||||
USER: "USER",
|
||||
API_KEY: "API_KEY"
|
||||
} as const;
|
||||
export type AuditLogRecordType = (typeof AuditLogRecordType)[keyof typeof AuditLogRecordType];
|
||||
export const JobType = {
|
||||
EVAL: "EVAL"
|
||||
} as const;
|
||||
@@ -75,6 +85,33 @@ export const JobExecutionStatus = {
|
||||
CANCELLED: "CANCELLED"
|
||||
} as const;
|
||||
export type JobExecutionStatus = (typeof JobExecutionStatus)[keyof typeof JobExecutionStatus];
|
||||
export const BlobStorageIntegrationFileType = {
|
||||
JSON: "JSON",
|
||||
CSV: "CSV",
|
||||
JSONL: "JSONL"
|
||||
} as const;
|
||||
export type BlobStorageIntegrationFileType = (typeof BlobStorageIntegrationFileType)[keyof typeof BlobStorageIntegrationFileType];
|
||||
export const BlobStorageIntegrationType = {
|
||||
S3: "S3",
|
||||
S3_COMPATIBLE: "S3_COMPATIBLE",
|
||||
AZURE_BLOB_STORAGE: "AZURE_BLOB_STORAGE"
|
||||
} as const;
|
||||
export type BlobStorageIntegrationType = (typeof BlobStorageIntegrationType)[keyof typeof BlobStorageIntegrationType];
|
||||
export const DashboardWidgetViews = {
|
||||
TRACES: "TRACES",
|
||||
OBSERVATIONS: "OBSERVATIONS",
|
||||
SCORES_NUMERIC: "SCORES_NUMERIC",
|
||||
SCORES_CATEGORICAL: "SCORES_CATEGORICAL"
|
||||
} as const;
|
||||
export type DashboardWidgetViews = (typeof DashboardWidgetViews)[keyof typeof DashboardWidgetViews];
|
||||
export const DashboardWidgetChartType = {
|
||||
LINE_TIME_SERIES: "LINE_TIME_SERIES",
|
||||
BAR_TIME_SERIES: "BAR_TIME_SERIES",
|
||||
HORIZONTAL_BAR: "HORIZONTAL_BAR",
|
||||
VERTICAL_BAR: "VERTICAL_BAR",
|
||||
PIE: "PIE"
|
||||
} as const;
|
||||
export type DashboardWidgetChartType = (typeof DashboardWidgetChartType)[keyof typeof DashboardWidgetChartType];
|
||||
export type Account = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
@@ -126,15 +163,19 @@ export type ApiKey = {
|
||||
display_secret_key: string;
|
||||
last_used_at: Timestamp | null;
|
||||
expires_at: Timestamp | null;
|
||||
project_id: string;
|
||||
project_id: string | null;
|
||||
organization_id: string | null;
|
||||
scope: Generated<ApiKeyScope>;
|
||||
};
|
||||
export type AuditLog = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
user_id: string;
|
||||
type: Generated<AuditLogRecordType>;
|
||||
api_key_id: string | null;
|
||||
user_id: string | null;
|
||||
org_id: string;
|
||||
user_org_role: string;
|
||||
user_org_role: string | null;
|
||||
project_id: string | null;
|
||||
user_project_role: string | null;
|
||||
resource_type: string;
|
||||
@@ -181,6 +222,24 @@ export type BillingMeterBackup = {
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type BlobStorageIntegration = {
|
||||
project_id: string;
|
||||
type: BlobStorageIntegrationType;
|
||||
bucket_name: string;
|
||||
prefix: string;
|
||||
access_key_id: string;
|
||||
secret_access_key: string;
|
||||
region: string;
|
||||
endpoint: string | null;
|
||||
force_path_style: boolean;
|
||||
next_sync_at: Timestamp | null;
|
||||
last_sync_at: Timestamp | null;
|
||||
enabled: boolean;
|
||||
export_frequency: string;
|
||||
file_type: Generated<BlobStorageIntegrationFileType>;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type Comment = {
|
||||
id: string;
|
||||
project_id: string;
|
||||
@@ -197,6 +256,33 @@ export type CronJobs = {
|
||||
job_started_at: Timestamp | null;
|
||||
state: string | null;
|
||||
};
|
||||
export type Dashboard = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
created_by: string | null;
|
||||
updated_by: string | null;
|
||||
project_id: string | null;
|
||||
name: string;
|
||||
description: string;
|
||||
definition: unknown;
|
||||
};
|
||||
export type DashboardWidget = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
created_by: string | null;
|
||||
updated_by: string | null;
|
||||
project_id: string | null;
|
||||
name: string;
|
||||
description: string;
|
||||
view: DashboardWidgetViews;
|
||||
dimensions: unknown;
|
||||
metrics: unknown;
|
||||
filters: unknown;
|
||||
chart_type: DashboardWidgetChartType;
|
||||
chart_config: unknown;
|
||||
};
|
||||
export type Dataset = {
|
||||
id: string;
|
||||
project_id: string;
|
||||
@@ -371,6 +457,24 @@ export type LlmApiKeys = {
|
||||
config: unknown | null;
|
||||
project_id: string;
|
||||
};
|
||||
export type LlmSchema = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
project_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
schema: unknown;
|
||||
};
|
||||
export type LlmTool = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
project_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: unknown;
|
||||
};
|
||||
export type Media = {
|
||||
id: string;
|
||||
sha_256_hash: string;
|
||||
@@ -427,6 +531,7 @@ export type Organization = {
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
cloud_config: unknown | null;
|
||||
metadata: unknown | null;
|
||||
};
|
||||
export type OrganizationMembership = {
|
||||
id: string;
|
||||
@@ -460,6 +565,7 @@ export type Project = {
|
||||
deleted_at: Timestamp | null;
|
||||
name: string;
|
||||
retention_days: number | null;
|
||||
metadata: unknown | null;
|
||||
};
|
||||
export type ProjectMembership = {
|
||||
org_membership_id: string;
|
||||
@@ -485,12 +591,22 @@ export type Prompt = {
|
||||
labels: Generated<string[]>;
|
||||
commit_message: string | null;
|
||||
};
|
||||
export type QueueBackUp = {
|
||||
export type PromptDependency = {
|
||||
id: string;
|
||||
project_id: string | null;
|
||||
queue_name: string;
|
||||
content: unknown;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
project_id: string;
|
||||
parent_id: string;
|
||||
child_name: string;
|
||||
child_label: string | null;
|
||||
child_version: number | null;
|
||||
};
|
||||
export type PromptProtectedLabels = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
project_id: string;
|
||||
label: string;
|
||||
};
|
||||
export type ScoreConfig = {
|
||||
id: string;
|
||||
@@ -562,8 +678,11 @@ export type DB = {
|
||||
background_migrations: BackgroundMigration;
|
||||
batch_exports: BatchExport;
|
||||
billing_meter_backups: BillingMeterBackup;
|
||||
blob_storage_integrations: BlobStorageIntegration;
|
||||
comments: Comment;
|
||||
cron_jobs: CronJobs;
|
||||
dashboard_widgets: DashboardWidget;
|
||||
dashboards: Dashboard;
|
||||
dataset_items: DatasetItem;
|
||||
dataset_run_items: DatasetRunItems;
|
||||
dataset_runs: DatasetRuns;
|
||||
@@ -572,6 +691,8 @@ export type DB = {
|
||||
job_configurations: JobConfiguration;
|
||||
job_executions: JobExecution;
|
||||
llm_api_keys: LlmApiKeys;
|
||||
llm_schemas: LlmSchema;
|
||||
llm_tools: LlmTool;
|
||||
media: Media;
|
||||
membership_invitations: MembershipInvitation;
|
||||
models: Model;
|
||||
@@ -583,8 +704,9 @@ export type DB = {
|
||||
prices: Price;
|
||||
project_memberships: ProjectMembership;
|
||||
projects: Project;
|
||||
prompt_dependencies: PromptDependency;
|
||||
prompt_protected_labels: PromptProtectedLabels;
|
||||
prompts: Prompt;
|
||||
queue_backups: QueueBackUp;
|
||||
score_configs: ScoreConfig;
|
||||
scores: LegacyPrismaScore;
|
||||
Session: Session;
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "prompt_dependencies" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"parent_id" TEXT NOT NULL,
|
||||
"child_name" TEXT NOT NULL,
|
||||
"child_label" TEXT,
|
||||
"child_version" INTEGER,
|
||||
|
||||
CONSTRAINT "prompt_dependencies_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "prompt_dependencies" ADD CONSTRAINT "prompt_dependencies_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "prompt_dependencies" ADD CONSTRAINT "prompt_dependencies_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "prompts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE INDEX "prompt_dependencies_project_id_parent_id" ON "prompt_dependencies"("project_id", "parent_id");
|
||||
CREATE INDEX "prompt_dependencies_project_id_child_name" ON "prompt_dependencies"("project_id", "child_name");
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AuditLogRecordType" AS ENUM ('USER', 'API_KEY');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "audit_logs" ADD COLUMN "api_key_id" TEXT,
|
||||
ADD COLUMN "type" "AuditLogRecordType" NOT NULL DEFAULT 'USER',
|
||||
ALTER COLUMN "user_id" DROP NOT NULL,
|
||||
ALTER COLUMN "user_org_role" DROP NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "audit_logs_api_key_id_idx" ON "audit_logs"("api_key_id");
|
||||
@@ -0,0 +1,2 @@
|
||||
-- DropTable
|
||||
DROP TABLE "queue_backups";
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BlobStorageIntegrationType" AS ENUM ('S3', 'S3_COMPATIBLE', 'AZURE_BLOB_STORAGE');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "blob_storage_integrations" (
|
||||
"project_id" TEXT NOT NULL,
|
||||
"type" "BlobStorageIntegrationType" NOT NULL,
|
||||
"bucket_name" TEXT NOT NULL,
|
||||
"prefix" TEXT NOT NULL,
|
||||
"access_key_id" TEXT NOT NULL,
|
||||
"secret_access_key" TEXT NOT NULL,
|
||||
"region" TEXT NOT NULL,
|
||||
"endpoint" TEXT,
|
||||
"force_path_style" BOOLEAN NOT NULL,
|
||||
"next_sync_at" TIMESTAMP(3),
|
||||
"last_sync_at" TIMESTAMP(3),
|
||||
"enabled" BOOLEAN NOT NULL,
|
||||
"export_frequency" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "blob_storage_integrations_pkey" PRIMARY KEY ("project_id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "blob_storage_integrations" ADD CONSTRAINT "blob_storage_integrations_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "llm_schemas" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"schema" JSON NOT NULL,
|
||||
|
||||
CONSTRAINT "llm_schemas_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "llm_tools" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"parameters" JSON NOT NULL,
|
||||
|
||||
CONSTRAINT "llm_tools_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "llm_schemas_project_id_name_key" ON "llm_schemas"("project_id", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "llm_tools_project_id_name_key" ON "llm_tools"("project_id", "name");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "llm_schemas" ADD CONSTRAINT "llm_schemas_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "llm_tools" ADD CONSTRAINT "llm_tools_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "prompt_protected_labels" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "prompt_protected_labels_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "prompt_protected_labels_project_id_label_key" ON "prompt_protected_labels"("project_id", "label");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "prompt_protected_labels" ADD CONSTRAINT "prompt_protected_labels_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BlobStorageIntegrationFileType" AS ENUM ('JSON', 'CSV', 'JSONL');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "blob_storage_integrations" ADD COLUMN "file_type" "BlobStorageIntegrationFileType" NOT NULL DEFAULT 'CSV';
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
-- Delete duplicate membership invitations, keeping only the newest one for each email and org_id combination
|
||||
WITH ranked_invitations AS (
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
org_id,
|
||||
ROW_NUMBER() OVER (PARTITION BY email, org_id ORDER BY updated_at DESC) as rn
|
||||
FROM membership_invitations
|
||||
)
|
||||
DELETE FROM membership_invitations
|
||||
WHERE id IN (
|
||||
SELECT id FROM ranked_invitations WHERE rn > 1
|
||||
);
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "membership_invitations_email_org_id_key" ON "membership_invitations"("email", "org_id");
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DashboardWidgetViews" AS ENUM ('TRACES', 'OBSERVATIONS', 'SCORES_NUMERIC', 'SCORES_CATEGORICAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DashboardWidgetChartType" AS ENUM ('LINE_TIME_SERIES', 'BAR_TIME_SERIES', 'HORIZONTAL_BAR', 'VERTICAL_BAR', 'PIE');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "dashboards" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"created_by" TEXT,
|
||||
"updated_by" TEXT,
|
||||
"project_id" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"definition" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "dashboards_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "dashboard_widgets" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"created_by" TEXT,
|
||||
"updated_by" TEXT,
|
||||
"project_id" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"view" "DashboardWidgetViews" NOT NULL,
|
||||
"dimensions" JSONB NOT NULL,
|
||||
"metrics" JSONB NOT NULL,
|
||||
"filters" JSONB NOT NULL,
|
||||
"chart_type" "DashboardWidgetChartType" NOT NULL,
|
||||
"chart_config" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "dashboard_widgets_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dashboards" ADD CONSTRAINT "dashboards_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dashboards" ADD CONSTRAINT "dashboards_updated_by_fkey" FOREIGN KEY ("updated_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dashboards" ADD CONSTRAINT "dashboards_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dashboard_widgets" ADD CONSTRAINT "dashboard_widgets_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dashboard_widgets" ADD CONSTRAINT "dashboard_widgets_updated_by_fkey" FOREIGN KEY ("updated_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dashboard_widgets" ADD CONSTRAINT "dashboard_widgets_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ApiKeyScope" AS ENUM ('ORGANIZATION', 'PROJECT');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "api_keys" ADD COLUMN "organization_id" TEXT,
|
||||
ADD COLUMN "scope" "ApiKeyScope" NOT NULL DEFAULT 'PROJECT',
|
||||
ALTER COLUMN "project_id" DROP NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "api_keys_organization_id_idx" ON "api_keys"("organization_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "organizations" ADD COLUMN "metadata" JSONB;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "projects" ADD COLUMN "metadata" JSONB;
|
||||
@@ -82,6 +82,10 @@ model User {
|
||||
featureFlags String[] @default([]) @map("feature_flags")
|
||||
annotatedLockedItem AnnotationQueueItem[] @relation("LockedByUser")
|
||||
annotatedCompletedItem AnnotationQueueItem[] @relation("AnnotatorUser")
|
||||
dashboardWidgetsCreated DashboardWidget[] @relation("CreatedByUser")
|
||||
dashboardWidgetsUpdated DashboardWidget[] @relation("UpdatedByUser")
|
||||
dashboardCreated Dashboard[] @relation("CreatedByUser")
|
||||
dashboardUpdated Dashboard[] @relation("UpdatedByUser")
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -101,63 +105,82 @@ model Organization {
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
cloudConfig Json? @map("cloud_config") // Langfuse Cloud, for zod schema see @/src/features/organizations/utils/cloudConfigSchema
|
||||
metadata Json?
|
||||
organizationMemberships OrganizationMembership[]
|
||||
projects Project[]
|
||||
MembershipInvitation MembershipInvitation[]
|
||||
ApiKey ApiKey[]
|
||||
|
||||
@@map("organizations")
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(cuid())
|
||||
orgId String @map("org_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
name String
|
||||
retentionDays Int? @map("retention_days")
|
||||
projectMembers ProjectMembership[]
|
||||
organization Organization @relation(fields: [orgId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
apiKeys ApiKey[]
|
||||
dataset Dataset[]
|
||||
invitations MembershipInvitation[]
|
||||
sessions TraceSession[]
|
||||
Prompt Prompt[]
|
||||
Model Model[]
|
||||
EvalTemplate EvalTemplate[]
|
||||
JobConfiguration JobConfiguration[]
|
||||
JobExecution JobExecution[]
|
||||
LlmApiKeys LlmApiKeys[]
|
||||
PosthogIntegration PosthogIntegration[]
|
||||
scoreConfig ScoreConfig[]
|
||||
BatchExport BatchExport[]
|
||||
comment Comment[]
|
||||
annotationQueue AnnotationQueue[]
|
||||
annotationQueueItem AnnotationQueueItem[]
|
||||
TraceMedia TraceMedia[]
|
||||
Media Media[]
|
||||
ObservationMedia ObservationMedia[]
|
||||
LegacyTrace LegacyPrismaTrace[]
|
||||
LegacyObservation LegacyPrismaObservation[]
|
||||
LegacyScore LegacyPrismaScore[]
|
||||
id String @id @default(cuid())
|
||||
orgId String @map("org_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
name String
|
||||
retentionDays Int? @map("retention_days")
|
||||
metadata Json?
|
||||
projectMembers ProjectMembership[]
|
||||
organization Organization @relation(fields: [orgId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
apiKeys ApiKey[]
|
||||
dataset Dataset[]
|
||||
invitations MembershipInvitation[]
|
||||
sessions TraceSession[]
|
||||
Prompt Prompt[]
|
||||
Model Model[]
|
||||
EvalTemplate EvalTemplate[]
|
||||
JobConfiguration JobConfiguration[]
|
||||
JobExecution JobExecution[]
|
||||
LlmApiKeys LlmApiKeys[]
|
||||
PosthogIntegration PosthogIntegration[]
|
||||
BlobStorageIntegration BlobStorageIntegration[]
|
||||
scoreConfig ScoreConfig[]
|
||||
BatchExport BatchExport[]
|
||||
comment Comment[]
|
||||
annotationQueue AnnotationQueue[]
|
||||
annotationQueueItem AnnotationQueueItem[]
|
||||
TraceMedia TraceMedia[]
|
||||
Media Media[]
|
||||
ObservationMedia ObservationMedia[]
|
||||
LegacyTrace LegacyPrismaTrace[]
|
||||
LegacyObservation LegacyPrismaObservation[]
|
||||
LegacyScore LegacyPrismaScore[]
|
||||
PromptDependency PromptDependency[]
|
||||
LlmSchema LlmSchema[]
|
||||
LlmTool LlmTool[]
|
||||
PromptProtectedLabels PromptProtectedLabels[]
|
||||
Dashboard Dashboard[]
|
||||
DashboardWidget DashboardWidget[]
|
||||
|
||||
@@index([orgId])
|
||||
@@map("projects")
|
||||
}
|
||||
|
||||
model ApiKey {
|
||||
id String @id @unique @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
note String?
|
||||
publicKey String @unique @map("public_key")
|
||||
hashedSecretKey String @unique @map("hashed_secret_key")
|
||||
fastHashedSecretKey String? @unique @map("fast_hashed_secret_key")
|
||||
displaySecretKey String @map("display_secret_key")
|
||||
lastUsedAt DateTime? @map("last_used_at")
|
||||
expiresAt DateTime? @map("expires_at")
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
enum ApiKeyScope {
|
||||
ORGANIZATION
|
||||
PROJECT
|
||||
}
|
||||
|
||||
model ApiKey {
|
||||
id String @id @unique @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
note String?
|
||||
publicKey String @unique @map("public_key")
|
||||
hashedSecretKey String @unique @map("hashed_secret_key")
|
||||
fastHashedSecretKey String? @unique @map("fast_hashed_secret_key")
|
||||
displaySecretKey String @map("display_secret_key")
|
||||
lastUsedAt DateTime? @map("last_used_at")
|
||||
expiresAt DateTime? @map("expires_at")
|
||||
projectId String? @map("project_id")
|
||||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
orgId String? @map("organization_id")
|
||||
organization Organization? @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
scope ApiKeyScope @default(PROJECT) @map("scope")
|
||||
|
||||
@@index(orgId)
|
||||
@@index(projectId)
|
||||
@@index(publicKey)
|
||||
@@index(hashedSecretKey)
|
||||
@@ -252,6 +275,7 @@ model MembershipInvitation {
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@unique([email, orgId]) // do not include projectId as this leads to issues when processing the invites, needs new logic
|
||||
@@index([projectId])
|
||||
@@index([orgId])
|
||||
@@index([email])
|
||||
@@ -293,6 +317,7 @@ model LegacyPrismaTrace {
|
||||
release String?
|
||||
version String?
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
public Boolean @default(false)
|
||||
bookmarked Boolean @default(false)
|
||||
tags String[] @default([])
|
||||
@@ -301,7 +326,6 @@ model LegacyPrismaTrace {
|
||||
sessionId String? @map("session_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
|
||||
@@index([projectId, timestamp])
|
||||
@@index([sessionId])
|
||||
@@ -318,6 +342,7 @@ model LegacyPrismaObservation {
|
||||
id String @id @default(cuid())
|
||||
traceId String? @map("trace_id")
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
type LegacyPrismaObservationType
|
||||
startTime DateTime @default(now()) @map("start_time")
|
||||
endTime DateTime? @map("end_time")
|
||||
@@ -349,7 +374,6 @@ model LegacyPrismaObservation {
|
||||
calculatedOutputCost Decimal? @map("calculated_output_cost")
|
||||
calculatedTotalCost Decimal? @map("calculated_total_cost")
|
||||
completionStartTime DateTime? @map("completion_start_time")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
promptId String? @map("prompt_id") // no fk constraint, prompt can be deleted
|
||||
|
||||
@@unique([id, projectId])
|
||||
@@ -371,6 +395,8 @@ enum LegacyPrismaObservationType {
|
||||
SPAN
|
||||
EVENT
|
||||
GENERATION
|
||||
|
||||
@@map("ObservationType")
|
||||
}
|
||||
|
||||
enum LegacyPrismaObservationLevel {
|
||||
@@ -378,12 +404,15 @@ enum LegacyPrismaObservationLevel {
|
||||
DEFAULT
|
||||
WARNING
|
||||
ERROR
|
||||
|
||||
@@map("ObservationLevel")
|
||||
}
|
||||
|
||||
model LegacyPrismaScore {
|
||||
id String @id @default(cuid())
|
||||
timestamp DateTime @default(now())
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
name String
|
||||
value Float? // always defined if data type is NUMERIC or BOOLEAN, optional for CATEGORICAL
|
||||
source LegacyPrismaScoreSource
|
||||
@@ -398,7 +427,6 @@ model LegacyPrismaScore {
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
dataType ScoreDataType @default(NUMERIC) @map("data_type")
|
||||
scoreConfig ScoreConfig? @relation(fields: [configId], references: [id], onDelete: SetNull)
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
|
||||
@@unique([id, projectId]) // used for upserts via prisma
|
||||
@@index(timestamp)
|
||||
@@ -417,6 +445,8 @@ enum LegacyPrismaScoreSource {
|
||||
ANNOTATION
|
||||
API
|
||||
EVAL
|
||||
|
||||
@@map("ScoreSource")
|
||||
}
|
||||
|
||||
model ScoreConfig {
|
||||
@@ -634,15 +664,16 @@ model Prompt {
|
||||
|
||||
createdBy String @map("created_by")
|
||||
|
||||
prompt Json
|
||||
name String
|
||||
version Int
|
||||
type String @default("text")
|
||||
isActive Boolean? @map("is_active") // Deprecated. To be removed once 'production' labels work as expected.
|
||||
config Json @default("{}") @db.Json
|
||||
tags String[] @default([])
|
||||
labels String[] @default([])
|
||||
commitMessage String? @map("commit_message")
|
||||
prompt Json
|
||||
name String
|
||||
version Int
|
||||
type String @default("text")
|
||||
isActive Boolean? @map("is_active") // Deprecated. To be removed once 'production' labels work as expected.
|
||||
config Json @default("{}") @db.Json
|
||||
tags String[] @default([])
|
||||
labels String[] @default([])
|
||||
commitMessage String? @map("commit_message")
|
||||
PromptDependency PromptDependency[]
|
||||
|
||||
@@unique([projectId, name, version])
|
||||
@@index([projectId, id])
|
||||
@@ -652,6 +683,38 @@ model Prompt {
|
||||
@@map("prompts")
|
||||
}
|
||||
|
||||
model PromptDependency {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
parentId String @map("parent_id")
|
||||
parent Prompt @relation(fields: [parentId], references: [id], onDelete: Cascade)
|
||||
|
||||
childName String @map("child_name")
|
||||
childLabel String? @map("child_label")
|
||||
childVersion Int? @map("child_version")
|
||||
|
||||
@@index([projectId, parentId], map: "prompt_dependencies_project_id_parent_id")
|
||||
@@index([projectId, childName], map: "prompt_dependencies_project_id_child_name")
|
||||
@@map("prompt_dependencies")
|
||||
}
|
||||
|
||||
model PromptProtectedLabels {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
label String
|
||||
|
||||
@@unique([projectId, label])
|
||||
@@map("prompt_protected_labels")
|
||||
}
|
||||
|
||||
// Update ObservationView below when making changes to this model!
|
||||
model Model {
|
||||
id String @id @default(cuid())
|
||||
@@ -690,23 +753,31 @@ model Price {
|
||||
@@map("prices")
|
||||
}
|
||||
|
||||
enum AuditLogRecordType {
|
||||
USER
|
||||
API_KEY
|
||||
}
|
||||
|
||||
// No FK constraints to preserve audit logs
|
||||
model AuditLog {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
userId String @map("user_id")
|
||||
orgId String @map("org_id")
|
||||
userOrgRole String @map("user_org_role")
|
||||
projectId String? @map("project_id")
|
||||
userProjectRole String? @map("user_project_role")
|
||||
resourceType String @map("resource_type")
|
||||
resourceId String @map("resource_id")
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
type AuditLogRecordType @default(USER)
|
||||
apiKeyId String? @map("api_key_id")
|
||||
userId String? @map("user_id")
|
||||
orgId String @map("org_id")
|
||||
userOrgRole String? @map("user_org_role")
|
||||
projectId String? @map("project_id")
|
||||
userProjectRole String? @map("user_project_role")
|
||||
resourceType String @map("resource_type")
|
||||
resourceId String @map("resource_id")
|
||||
action String
|
||||
before String? //stringified JSON
|
||||
before String? // stringified JSON
|
||||
after String? // stringified JSON
|
||||
|
||||
@@index([projectId])
|
||||
@@index([apiKeyId])
|
||||
@@index([userId])
|
||||
@@index([orgId])
|
||||
@@index([createdAt])
|
||||
@@ -822,9 +893,9 @@ model SsoConfig {
|
||||
authProvider String @map("auth_provider") // e.g. "okta", ee/sso/types.ts
|
||||
|
||||
authConfig Json? @map("auth_config")
|
||||
|
||||
// e.g. { "clientId": "1234", "clientSecret": "5678" }, null if credentials from env should be used
|
||||
// secrets like clientSecret are encrypted on the application level
|
||||
|
||||
@@map("sso_configs")
|
||||
}
|
||||
|
||||
@@ -840,6 +911,49 @@ model PosthogIntegration {
|
||||
@@map("posthog_integrations")
|
||||
}
|
||||
|
||||
model BlobStorageIntegration {
|
||||
projectId String @id @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
// Blob Storage Config
|
||||
type BlobStorageIntegrationType @map("type")
|
||||
bucketName String @map("bucket_name")
|
||||
prefix String @map("prefix")
|
||||
accessKeyId String @map("access_key_id")
|
||||
secretAccessKey String @map("secret_access_key")
|
||||
region String @map("region")
|
||||
endpoint String? @map("endpoint")
|
||||
forcePathStyle Boolean @map("force_path_style")
|
||||
|
||||
// Integration Config
|
||||
nextSyncAt DateTime? @map("next_sync_at")
|
||||
lastSyncAt DateTime? @map("last_sync_at")
|
||||
enabled Boolean
|
||||
exportFrequency String @map("export_frequency")
|
||||
fileType BlobStorageIntegrationFileType @default(CSV) @map("file_type")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@map("blob_storage_integrations")
|
||||
}
|
||||
|
||||
enum BlobStorageIntegrationFileType {
|
||||
JSON
|
||||
CSV
|
||||
JSONL
|
||||
|
||||
@@map("BlobStorageIntegrationFileType")
|
||||
}
|
||||
|
||||
enum BlobStorageIntegrationType {
|
||||
S3
|
||||
S3_COMPATIBLE
|
||||
AZURE_BLOB_STORAGE
|
||||
|
||||
@@map("BlobStorageIntegrationType")
|
||||
}
|
||||
|
||||
model BatchExport {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
@@ -917,17 +1031,6 @@ model ObservationMedia {
|
||||
@@map("observation_media")
|
||||
}
|
||||
|
||||
model QueueBackUp {
|
||||
id String @id @default(cuid())
|
||||
projectId String? @map("project_id")
|
||||
queueName String @map("queue_name")
|
||||
content Json
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("queue_backups")
|
||||
}
|
||||
|
||||
model BillingMeterBackup {
|
||||
// unique
|
||||
stripeCustomerId String @map("stripe_customer_id")
|
||||
@@ -950,3 +1053,100 @@ model BillingMeterBackup {
|
||||
@@index([stripeCustomerId, meterId, startTime, endTime])
|
||||
@@map("billing_meter_backups")
|
||||
}
|
||||
|
||||
model LlmSchema {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
name String @map("name")
|
||||
description String @map("description")
|
||||
schema Json @map("schema") @db.Json
|
||||
|
||||
@@unique([projectId, name])
|
||||
@@map("llm_schemas")
|
||||
}
|
||||
|
||||
model LlmTool {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
name String @map("name")
|
||||
description String @map("description")
|
||||
parameters Json @map("parameters") @db.Json
|
||||
|
||||
@@unique([projectId, name])
|
||||
@@map("llm_tools")
|
||||
}
|
||||
|
||||
model Dashboard {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
createdBy String? @map("created_by")
|
||||
createdByUser User? @relation("CreatedByUser", fields: [createdBy], references: [id], onDelete: SetNull)
|
||||
updatedBy String? @map("updated_by")
|
||||
updatedByUser User? @relation("UpdatedByUser", fields: [updatedBy], references: [id], onDelete: SetNull)
|
||||
|
||||
// Optional to account for Langfuse-managed dashboards
|
||||
projectId String? @map("project_id")
|
||||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
name String @map("name")
|
||||
description String @map("description")
|
||||
|
||||
definition Json @map("definition")
|
||||
|
||||
@@map("dashboards")
|
||||
}
|
||||
|
||||
enum DashboardWidgetViews {
|
||||
TRACES
|
||||
OBSERVATIONS
|
||||
SCORES_NUMERIC
|
||||
SCORES_CATEGORICAL
|
||||
}
|
||||
|
||||
enum DashboardWidgetChartType {
|
||||
LINE_TIME_SERIES
|
||||
BAR_TIME_SERIES
|
||||
HORIZONTAL_BAR
|
||||
VERTICAL_BAR
|
||||
PIE
|
||||
}
|
||||
|
||||
model DashboardWidget {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
createdBy String? @map("created_by")
|
||||
createdByUser User? @relation("CreatedByUser", fields: [createdBy], references: [id], onDelete: SetNull)
|
||||
updatedBy String? @map("updated_by")
|
||||
updatedByUser User? @relation("UpdatedByUser", fields: [updatedBy], references: [id], onDelete: SetNull)
|
||||
|
||||
// Optional to account for Langfuse-managed dashboards
|
||||
projectId String? @map("project_id")
|
||||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
name String @map("name")
|
||||
description String @map("description")
|
||||
|
||||
view DashboardWidgetViews @map("view")
|
||||
dimensions Json @map("dimensions")
|
||||
metrics Json @map("metrics")
|
||||
filters Json @map("filters")
|
||||
|
||||
chartType DashboardWidgetChartType @map("chart_type")
|
||||
chartConfig Json @map("chart_config")
|
||||
|
||||
@@map("dashboard_widgets")
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ModelUsageUnit } from "../src";
|
||||
import { getDisplaySecretKey, hashSecretKey, logger } from "../src/server";
|
||||
import { encrypt } from "../src/encryption";
|
||||
import { redis } from "../src/server/redis/redis";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
const LOAD_TRACE_VOLUME = 10_000;
|
||||
|
||||
@@ -72,6 +73,9 @@ async function main() {
|
||||
where: { id: seedOrgId },
|
||||
update: {
|
||||
name: "Seed Org",
|
||||
cloudConfig: {
|
||||
plan: "Team",
|
||||
},
|
||||
},
|
||||
create: {
|
||||
id: seedOrgId,
|
||||
@@ -177,6 +181,7 @@ async function main() {
|
||||
publicKey: seedApiKey.public,
|
||||
hashedSecretKey: await hashSecretKey(seedApiKey.secret),
|
||||
displaySecretKey: getDisplaySecretKey(seedApiKey.secret),
|
||||
scope: "PROJECT",
|
||||
project: {
|
||||
connect: {
|
||||
id: project1.id,
|
||||
@@ -238,6 +243,7 @@ async function main() {
|
||||
publicKey: secondKey.public,
|
||||
hashedSecretKey: await hashSecretKey(secondKey.secret),
|
||||
displaySecretKey: getDisplaySecretKey(secondKey.secret),
|
||||
scope: "PROJECT",
|
||||
project: {
|
||||
connect: {
|
||||
id: project2.id,
|
||||
@@ -371,6 +377,48 @@ async function main() {
|
||||
});
|
||||
|
||||
await createDatasets(project1, project2, observations);
|
||||
|
||||
await createDashboardsAndWidgets([project1, project2]);
|
||||
|
||||
await prisma.llmSchema.createMany({
|
||||
data: [
|
||||
{
|
||||
projectId: project1.id,
|
||||
name: "get_weather",
|
||||
description: "Fetches weather in Celsius for a given location",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
description: "The city and state, e.g. San Francisco, CA",
|
||||
},
|
||||
unit: {
|
||||
type: "string",
|
||||
enum: ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
required: ["location", "unit"],
|
||||
},
|
||||
},
|
||||
{
|
||||
projectId: project1.id,
|
||||
name: "calculator",
|
||||
description: "Performs basic arithmetic calculations",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
expression: {
|
||||
type: "string",
|
||||
description:
|
||||
"The mathematical expression to evaluate, e.g. '2 + 2'",
|
||||
},
|
||||
},
|
||||
required: ["expression"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,6 +436,85 @@ main()
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
async function createDashboardsAndWidgets(projects: Project[]) {
|
||||
logger.info("Creating dashboards and widgets");
|
||||
|
||||
// Process each project
|
||||
for (const project of projects) {
|
||||
const widget = await prisma.dashboardWidget.upsert({
|
||||
where: { id: "cabc" },
|
||||
create: {
|
||||
id: "cabc",
|
||||
projectId: project.id,
|
||||
name: "Trace Counts",
|
||||
description: "Trace Counts by Name Over Time",
|
||||
view: "TRACES",
|
||||
dimensions: [{ field: "name" }],
|
||||
metrics: [{ measure: "count", agg: "count" }],
|
||||
filters: [],
|
||||
chartType: "BAR_TIME_SERIES",
|
||||
chartConfig: {
|
||||
type: "BAR_TIME_SERIES",
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
const widget2 = await prisma.dashboardWidget.upsert({
|
||||
where: { id: "cdef" },
|
||||
create: {
|
||||
id: "cdef",
|
||||
projectId: project.id,
|
||||
name: "Observation Latencies by Model",
|
||||
description: "p95 Observation Latencies by Model Name",
|
||||
view: "OBSERVATIONS",
|
||||
dimensions: [{ field: "providedModelName" }],
|
||||
metrics: [{ measure: "count", agg: "sum" }],
|
||||
filters: [],
|
||||
chartType: "LINE_TIME_SERIES",
|
||||
chartConfig: {
|
||||
type: "LINE_TIME_SERIES",
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
// Create a dashboard with multiple widgets
|
||||
await prisma.dashboard.upsert({
|
||||
where: { id: "seed-dashboard" },
|
||||
create: {
|
||||
id: "seed-dashboard",
|
||||
projectId: project.id,
|
||||
name: "Performance Overview",
|
||||
description: "Dashboard with various performance metrics",
|
||||
definition: {
|
||||
widgets: [
|
||||
{
|
||||
type: "widget",
|
||||
id: randomUUID(),
|
||||
widgetId: widget.id,
|
||||
x: 0,
|
||||
y: 0,
|
||||
x_size: 6,
|
||||
y_size: 2,
|
||||
},
|
||||
{
|
||||
type: "widget",
|
||||
id: randomUUID(),
|
||||
widgetId: widget2.id,
|
||||
x: 6,
|
||||
y: 0,
|
||||
x_size: 6,
|
||||
y_size: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function createDatasets(
|
||||
project1: {
|
||||
id: string;
|
||||
@@ -727,6 +854,7 @@ function createObjects(
|
||||
source: "API",
|
||||
projectId,
|
||||
dataType: ScoreDataType.NUMERIC,
|
||||
metadata: {},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -742,6 +870,7 @@ function createObjects(
|
||||
dataType: ScoreDataType.CATEGORICAL,
|
||||
stringValue:
|
||||
Math.floor(Math.random() * 2) === 1 ? "Fully" : "Partially",
|
||||
metadata: {},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
@@ -27,6 +27,9 @@ const prepareProjectsAndApiKeys = async (
|
||||
create: {
|
||||
id: orgId,
|
||||
name: `Organization for ${projectId}`,
|
||||
cloudConfig: {
|
||||
plan: "Team",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -55,6 +58,7 @@ const prepareProjectsAndApiKeys = async (
|
||||
publicKey: `pk-${Math.random().toString(36).substr(2, 9)}`,
|
||||
hashedSecretKey: sk,
|
||||
displaySecretKey: getDisplaySecretKey(sk),
|
||||
scope: "PROJECT",
|
||||
project: {
|
||||
connect: {
|
||||
id: projectId,
|
||||
|
||||
@@ -145,7 +145,7 @@ export const prepareClickhouse = async (
|
||||
|
||||
const scoresQuery = `
|
||||
INSERT INTO scores
|
||||
SELECT toString(floor(randUniform(0, 100))) AS id,
|
||||
SELECT toString(number) AS id,
|
||||
toDateTime(now() - randUniform(0, ${opts.numberOfDays} * 24 * 60 * 60)) AS timestamp,
|
||||
'${projectId}' AS project_id,
|
||||
'default' AS environment,
|
||||
@@ -159,6 +159,7 @@ export const prepareClickhouse = async (
|
||||
randUniform(0, 100) as value,
|
||||
'API' as source,
|
||||
'comment' as comment,
|
||||
map('key', 'value') AS metadata,
|
||||
toString(rand() % 100) as author_user_id,
|
||||
toString(rand() % 100) as config_id,
|
||||
if (rand() < 0.33, 'NUMERIC', if (rand() < 0.5, 'CATEGORICAL', 'BOOLEAN')) as data_type,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./observations";
|
||||
export * from "./traces";
|
||||
export * from "./scores";
|
||||
@@ -0,0 +1,76 @@
|
||||
import z from "zod";
|
||||
import { jsonSchema } from "../utils/zod";
|
||||
import { MetadataDomain } from "./traces";
|
||||
|
||||
export const ObservationType = {
|
||||
SPAN: "SPAN",
|
||||
EVENT: "EVENT",
|
||||
GENERATION: "GENERATION",
|
||||
} as const;
|
||||
export const ObservationTypeDomain = z.enum(["SPAN", "EVENT", "GENERATION"]);
|
||||
export type ObservationType = z.infer<typeof ObservationTypeDomain>;
|
||||
|
||||
export const ObservationLevel = {
|
||||
DEBUG: "DEBUG",
|
||||
DEFAULT: "DEFAULT",
|
||||
WARNING: "WARNING",
|
||||
ERROR: "ERROR",
|
||||
} as const;
|
||||
export const ObservationLevelDomain = z.enum([
|
||||
"DEBUG",
|
||||
"DEFAULT",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
]);
|
||||
export type ObservationLevelType = z.infer<typeof ObservationLevelDomain>;
|
||||
|
||||
export const ObservationDomain = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
timestamp: z.date(),
|
||||
environment: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
});
|
||||
export type ObservationDomain = z.infer<typeof ObservationDomain>;
|
||||
|
||||
export const ObservationSchema = z.object({
|
||||
id: z.string(),
|
||||
traceId: z.string().nullable(),
|
||||
projectId: z.string(),
|
||||
environment: z.string(),
|
||||
type: ObservationTypeDomain,
|
||||
startTime: z.date(),
|
||||
endTime: z.date().nullable(),
|
||||
name: z.string().nullable(),
|
||||
metadata: MetadataDomain,
|
||||
parentObservationId: z.string().nullable(),
|
||||
level: ObservationLevelDomain,
|
||||
statusMessage: z.string().nullable(),
|
||||
version: z.string().nullable(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
model: z.string().nullable(),
|
||||
internalModelId: z.string().nullable(),
|
||||
modelParameters: jsonSchema.nullable(),
|
||||
input: jsonSchema.nullable(),
|
||||
output: jsonSchema.nullable(),
|
||||
completionStartTime: z.date().nullable(),
|
||||
promptId: z.string().nullable(),
|
||||
promptName: z.string().nullable(),
|
||||
promptVersion: z.number().nullable(),
|
||||
latency: z.number().nullable(),
|
||||
timeToFirstToken: z.number().nullable(),
|
||||
usageDetails: z.record(z.string(), z.number()),
|
||||
costDetails: z.record(z.string(), z.number()),
|
||||
providedCostDetails: z.record(z.string(), z.number()),
|
||||
// aggregated data from cost_details
|
||||
inputCost: z.number().nullable(),
|
||||
outputCost: z.number().nullable(),
|
||||
totalCost: z.number().nullable(),
|
||||
// aggregated data from usage_details
|
||||
inputUsage: z.number(),
|
||||
outputUsage: z.number(),
|
||||
totalUsage: z.number(),
|
||||
});
|
||||
|
||||
export type Observation = z.infer<typeof ObservationSchema>;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ScoreDataType } from "@prisma/client";
|
||||
import z from "zod";
|
||||
import { MetadataDomain } from "./traces";
|
||||
|
||||
export const ScoreSource = {
|
||||
ANNOTATION: "ANNOTATION",
|
||||
API: "API",
|
||||
EVAL: "EVAL",
|
||||
} as const;
|
||||
|
||||
export const ScoreSourceDomain = z.enum(["ANNOTATION", "API", "EVAL"]);
|
||||
export type ScoreSourceType = z.infer<typeof ScoreSourceDomain>;
|
||||
|
||||
export const ScoreSchema = z.object({
|
||||
id: z.string(),
|
||||
timestamp: z.date(),
|
||||
projectId: z.string(),
|
||||
environment: z.string(),
|
||||
name: z.string(),
|
||||
value: z.number().nullable(),
|
||||
source: ScoreSourceDomain,
|
||||
authorUserId: z.string().nullable(),
|
||||
comment: z.string().nullable(),
|
||||
metadata: MetadataDomain,
|
||||
traceId: z.string(),
|
||||
observationId: z.string().nullable(),
|
||||
configId: z.string().nullable(),
|
||||
stringValue: z.string().nullable(),
|
||||
queueId: z.string().nullable(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
dataType: z.nativeEnum(ScoreDataType),
|
||||
});
|
||||
|
||||
export type ScoreDomain = z.infer<typeof ScoreSchema>;
|
||||
@@ -0,0 +1,32 @@
|
||||
import z from "zod";
|
||||
import { jsonSchema, jsonSchemaNullable } from "../utils/zod";
|
||||
|
||||
export const MetadataDomain = z.record(
|
||||
z.string(),
|
||||
jsonSchemaNullable.or(z.undefined()),
|
||||
);
|
||||
|
||||
export type MetadataDomain = z.infer<typeof MetadataDomain>;
|
||||
|
||||
// to be used across the application in frontend and backend.
|
||||
export const TraceDomain = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().nullable(),
|
||||
timestamp: z.date(),
|
||||
environment: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
bookmarked: z.boolean(),
|
||||
public: z.boolean(),
|
||||
release: z.string().nullable(),
|
||||
version: z.string().nullable(),
|
||||
input: jsonSchema.nullable(),
|
||||
output: jsonSchema.nullable(),
|
||||
metadata: MetadataDomain,
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
sessionId: z.string().nullable(),
|
||||
userId: z.string().nullable(),
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
export type TraceDomain = z.infer<typeof TraceDomain>;
|
||||
@@ -2,7 +2,7 @@ import crypto from "crypto";
|
||||
import { env } from "../env";
|
||||
|
||||
const ENCRYPTION_KEY: string | undefined = env.ENCRYPTION_KEY; // Must be 256 bits (32 bytes, 64 hex characters)
|
||||
const IV_LENGTH: number = 16; // For AES, this is always 16
|
||||
const IV_LENGTH: number = 12; // For AES-GCM, this is always 12
|
||||
|
||||
// Alternatively: openssl rand -hex 32
|
||||
export function keyGen() {
|
||||
@@ -23,7 +23,7 @@ export function encrypt(plainText: string): string {
|
||||
const cipher = crypto.createCipheriv(
|
||||
"aes-256-gcm",
|
||||
Buffer.from(ENCRYPTION_KEY, "hex"),
|
||||
iv
|
||||
iv,
|
||||
);
|
||||
let encrypted = cipher.update(plainText, "utf8", "hex");
|
||||
encrypted += cipher.final("hex");
|
||||
@@ -49,7 +49,7 @@ export function decrypt(text: string): string {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
"aes-256-gcm",
|
||||
Buffer.from(ENCRYPTION_KEY, "hex"),
|
||||
iv
|
||||
iv,
|
||||
);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
import { removeEmptyEnvVariables } from "./utils/environment";
|
||||
|
||||
const EnvSchema = z.object({
|
||||
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION: z.string().optional(),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "test", "production"])
|
||||
.default("development"),
|
||||
@@ -10,7 +11,7 @@ const EnvSchema = z.object({
|
||||
REDIS_PORT: z.coerce
|
||||
.number({
|
||||
description:
|
||||
".env files convert numbers to strings, therefoore we have to enforce them to be numbers",
|
||||
".env files convert numbers to strings, therefore we have to enforce them to be numbers",
|
||||
})
|
||||
.positive()
|
||||
.max(65536, `options.port should be >= 0 and < 65536`)
|
||||
@@ -50,6 +51,7 @@ const EnvSchema = z.object({
|
||||
ENABLE_AWS_CLOUDWATCH_METRIC_PUBLISHING: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_S3_CONCURRENT_WRITES: z.coerce.number().positive().default(50),
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: z.string({
|
||||
required_error: "Langfuse requires a bucket name for S3 Event Uploads.",
|
||||
}),
|
||||
@@ -62,6 +64,8 @@ const EnvSchema = z.object({
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_USE_AZURE_BLOB: z.enum(["true", "false"]).default("false"),
|
||||
LANGFUSE_USE_GOOGLE_CLOUD_STORAGE: z.enum(["true", "false"]).default("false"),
|
||||
LANGFUSE_GOOGLE_CLOUD_STORAGE_CREDENTIALS: z.string().optional(),
|
||||
STRIPE_SECRET_KEY: z.string().optional(),
|
||||
|
||||
LANGFUSE_S3_CORE_DATA_EXPORT_IS_ENABLED: z
|
||||
@@ -70,6 +74,10 @@ const EnvSchema = z.object({
|
||||
LANGFUSE_POSTGRES_METERING_DATA_EXPORT_IS_ENABLED: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
|
||||
LANGFUSE_CUSTOM_SSO_EMAIL_CLAIM: z.string().default("email"),
|
||||
LANGFUSE_CUSTOM_SSO_NAME_CLAIM: z.string().default("name"),
|
||||
LANGFUSE_CUSTOM_SSO_SUB_CLAIM: z.string().default("sub"),
|
||||
});
|
||||
|
||||
export const env: z.infer<typeof EnvSchema> =
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { BaseError } from "./BaseError";
|
||||
|
||||
export class LangfuseConflictError extends BaseError {
|
||||
constructor(description = "Conflict") {
|
||||
super("LangfuseConflictError", 409, description, true);
|
||||
}
|
||||
}
|
||||
@@ -6,3 +6,4 @@ export { ForbiddenError } from "./ForbiddenError";
|
||||
export { MethodNotAllowedError } from "./MethodNotAllowedError";
|
||||
export { ApiError } from "./ApiError";
|
||||
export { InternalServerError } from "./InternalServerError";
|
||||
export { LangfuseConflictError } from "./ConflictError";
|
||||
|
||||
@@ -10,6 +10,7 @@ export enum BatchActionType {
|
||||
}
|
||||
|
||||
const ActionIdSchema = z.enum([
|
||||
"score-delete",
|
||||
"trace-delete",
|
||||
"trace-add-to-annotation-queue",
|
||||
]);
|
||||
|
||||
@@ -15,12 +15,14 @@ export enum BatchExportStatus {
|
||||
export enum BatchExportFileFormat {
|
||||
JSON = "JSON",
|
||||
CSV = "CSV",
|
||||
JSONL = "JSONL",
|
||||
}
|
||||
|
||||
export enum BatchExportTableName {
|
||||
Scores = "scores",
|
||||
Sessions = "sessions",
|
||||
Traces = "traces",
|
||||
Generations = "generations",
|
||||
Observations = "observations",
|
||||
DatasetRunItems = "dataset_run_items",
|
||||
}
|
||||
|
||||
@@ -34,6 +36,11 @@ export const exportOptions: Record<
|
||||
> = {
|
||||
CSV: { label: "CSV", extension: "csv", fileType: "text/csv" },
|
||||
JSON: { label: "JSON", extension: "json", fileType: "application/json" },
|
||||
JSONL: {
|
||||
label: "JSONL",
|
||||
extension: "jsonl",
|
||||
fileType: "application/x-ndjson",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const BatchExportQuerySchema = z.object({
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
// used on organization.cloudConfig.plan
|
||||
export const cloudConfigPlans = [
|
||||
"Hobby",
|
||||
"Core",
|
||||
"Pro",
|
||||
"Team",
|
||||
"Enterprise",
|
||||
] as const;
|
||||
|
||||
export const planLabels = {
|
||||
oss: "OSS",
|
||||
"cloud:hobby": "Hobby",
|
||||
"cloud:core": "Core",
|
||||
"cloud:pro": "Pro",
|
||||
"cloud:team": "Team",
|
||||
"cloud:enterprise": "Enterprise",
|
||||
"self-hosted:pro": "Pro (self-hosted)",
|
||||
"self-hosted:enterprise": "Enterprise (self-hosted)",
|
||||
} as const;
|
||||
@@ -12,8 +23,9 @@ export type Plan = keyof typeof planLabels;
|
||||
export const plans = Object.keys(planLabels) as Plan[];
|
||||
|
||||
// These functions are kept here to ensure consistency when updating plan names in the future.
|
||||
export const isCloudPlan = (plan: Plan) => plan.startsWith("cloud");
|
||||
export const isSelfHostedPlan = (plan: Plan) => plan.startsWith("self-hosted");
|
||||
export const isCloudPlan = (plan?: Plan) => plan?.startsWith("cloud");
|
||||
export const isSelfHostedPlan = (plan?: Plan) =>
|
||||
plan?.startsWith("self-hosted");
|
||||
|
||||
export const isPlan = (value: string): value is Plan =>
|
||||
plans.includes(value as Plan);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const PromptDependencyRegex = /@@@langfusePrompt:(.*?)@@@/g;
|
||||
|
||||
export const ParsedPromptDependencySchema = z.union([
|
||||
z.object({
|
||||
name: z.string(),
|
||||
type: z.literal("version"),
|
||||
version: z.coerce.number(),
|
||||
}),
|
||||
z.object({ name: z.string(), type: z.literal("label"), label: z.string() }),
|
||||
]);
|
||||
|
||||
export type ParsedPromptDependencyTag = z.infer<
|
||||
typeof ParsedPromptDependencySchema
|
||||
>;
|
||||
|
||||
export function parsePromptDependencyTags(
|
||||
content: string | object,
|
||||
): ParsedPromptDependencyTag[] {
|
||||
const matchedTags = JSON.stringify(content).match(PromptDependencyRegex);
|
||||
|
||||
const validTags: ParsedPromptDependencyTag[] = [];
|
||||
|
||||
for (const match of new Set(matchedTags ?? [])) {
|
||||
const innerContent = match.replace(/^@@@langfusePrompt:|@@@$/g, "");
|
||||
const parts = innerContent.split("|");
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
// Check if the first parameter is name
|
||||
const firstPart = parts[0];
|
||||
if (!firstPart || !firstPart.startsWith("name=")) {
|
||||
continue; // Skip this tag if name is not the first parameter. This makes it easier to replace the tag with the resolved prompt.
|
||||
}
|
||||
|
||||
// There can be only 2 parts
|
||||
if (parts.length !== 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
parts.forEach((part) => {
|
||||
const [key, value] = part.split("=");
|
||||
params[key] = value;
|
||||
});
|
||||
|
||||
if (params.name) {
|
||||
const parsed = ParsedPromptDependencySchema.safeParse({
|
||||
name: params.name,
|
||||
...(params.version
|
||||
? { version: params.version, type: "version" }
|
||||
: { label: params.label, type: "label" }),
|
||||
});
|
||||
|
||||
if (parsed.success) {
|
||||
validTags.push(parsed.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validTags;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { isPresent, stringDateTime } from "../../utils/typeChecks";
|
||||
import { Score } from "../../server";
|
||||
import {
|
||||
jsonSchema,
|
||||
NonEmptyString,
|
||||
paginationMetaResponseZod,
|
||||
publicApiPaginationZod,
|
||||
} from "../../utils/zod";
|
||||
import { Category as ConfigCategory } from "./scoreConfigTypes";
|
||||
import { ScoreDomain } from "../../domain";
|
||||
|
||||
/**
|
||||
* Types to use across codebase
|
||||
@@ -50,6 +51,7 @@ const ScoreBase = z.object({
|
||||
source: z.enum(ScoreSource),
|
||||
authorUserId: z.string().nullish(),
|
||||
comment: z.string().nullish(),
|
||||
metadata: jsonSchema.nullish(),
|
||||
traceId: z.string(),
|
||||
observationId: z.string().nullish(),
|
||||
configId: z.string().nullish(),
|
||||
@@ -64,6 +66,8 @@ const BaseScoreBody = z.object({
|
||||
traceId: z.string(),
|
||||
observationId: z.string().nullish(),
|
||||
comment: z.string().nullish(),
|
||||
metadata: jsonSchema.nullish(),
|
||||
environment: z.string().default("default"),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -154,26 +158,45 @@ export const ScorePropsAgainstConfig = z.union([
|
||||
* Transformations
|
||||
*/
|
||||
|
||||
type ValidatedAPIScore<IncludeHasMetadata extends boolean> = APIScore & {
|
||||
hasMetadata: IncludeHasMetadata extends true ? boolean : never;
|
||||
};
|
||||
|
||||
type InputScore = ScoreDomain & { hasMetadata?: boolean };
|
||||
|
||||
/**
|
||||
* Use this function when pulling a list of scores from the database before using in the application to ensure type safety.
|
||||
* All scores are expected to pass the validation. If a score fails validation, it will be logged to Otel.
|
||||
* @param scores
|
||||
* @returns list of validated scores
|
||||
*/
|
||||
export const filterAndValidateDbScoreList = (
|
||||
scores: Score[],
|
||||
onParseError?: (error: z.ZodError) => void,
|
||||
): APIScore[] =>
|
||||
scores.reduce((acc, ts) => {
|
||||
export const filterAndValidateDbScoreList = <
|
||||
IncludeHasMetadata extends boolean,
|
||||
>({
|
||||
scores,
|
||||
includeHasMetadata = false as IncludeHasMetadata,
|
||||
onParseError,
|
||||
}: {
|
||||
scores: InputScore[];
|
||||
includeHasMetadata?: IncludeHasMetadata;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onParseError?: (error: z.ZodError) => void;
|
||||
}): ValidatedAPIScore<IncludeHasMetadata>[] => {
|
||||
return scores.reduce((acc, ts) => {
|
||||
const result = APIScoreSchema.safeParse(ts);
|
||||
if (result.success) {
|
||||
acc.push(result.data);
|
||||
const score = { ...result.data };
|
||||
if (includeHasMetadata) {
|
||||
Object.assign(score, { hasMetadata: ts.hasMetadata ?? false });
|
||||
}
|
||||
acc.push(score as ValidatedAPIScore<IncludeHasMetadata>);
|
||||
} else {
|
||||
console.error("Score parsing error: ", result.error);
|
||||
onParseError?.(result.error);
|
||||
}
|
||||
return acc;
|
||||
}, [] as APIScore[]);
|
||||
}, [] as ValidatedAPIScore<IncludeHasMetadata>[]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this function when pulling a single score from the database before using in the application to ensure type safety.
|
||||
@@ -182,7 +205,7 @@ export const filterAndValidateDbScoreList = (
|
||||
* @returns validated score
|
||||
* @throws error if score fails validation
|
||||
*/
|
||||
export const validateDbScore = (score: Score): APIScore =>
|
||||
export const validateDbScore = (score: ScoreDomain): APIScore =>
|
||||
APIScoreSchema.parse(score);
|
||||
|
||||
/**
|
||||
@@ -237,6 +260,7 @@ export const GetScoresQuery = z.object({
|
||||
configId: z.string().nullish(),
|
||||
queueId: z.string().nullish(),
|
||||
traceTags: z.union([z.array(z.string()), z.string()]).nullish(),
|
||||
environment: z.union([z.array(z.string()), z.string()]).nullish(),
|
||||
name: z.string().nullish(),
|
||||
fromTimestamp: stringDateTime,
|
||||
toTimestamp: stringDateTime,
|
||||
@@ -259,6 +283,7 @@ const LegacyGetScoreResponseDataV1 = z.intersection(
|
||||
trace: z.object({
|
||||
userId: z.string().nullish(),
|
||||
tags: z.array(z.string()).nullish(),
|
||||
environment: z.string().nullish(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -269,6 +294,7 @@ export const GetScoresResponse = z.object({
|
||||
|
||||
export const legacyFilterAndValidateV1GetScoreList = (
|
||||
scores: unknown[],
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onParseError?: (error: z.ZodError) => void,
|
||||
): z.infer<typeof LegacyGetScoreResponseDataV1>[] =>
|
||||
scores.reduce(
|
||||
@@ -277,7 +303,7 @@ export const legacyFilterAndValidateV1GetScoreList = (
|
||||
if (result.success) {
|
||||
acc.push(result.data);
|
||||
} else {
|
||||
console.error("Score parsing error: ", result.error);
|
||||
console.error(`Score parsing error ${JSON.stringify(result.error)}`);
|
||||
onParseError?.(result.error);
|
||||
}
|
||||
return acc;
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { ScoreDataType } from "@prisma/client";
|
||||
import { ScoreSourceType } from "../../server";
|
||||
import { ScoreSourceType, type MetadataDomain } from "../../domain";
|
||||
|
||||
export type CategoricalAggregate = {
|
||||
export type BaseAggregate = {
|
||||
comment?: string | null;
|
||||
id?: string | null;
|
||||
hasMetadata?: boolean | null;
|
||||
};
|
||||
|
||||
export type CategoricalAggregate = BaseAggregate & {
|
||||
type: "CATEGORICAL";
|
||||
values: string[];
|
||||
valueCounts: { value: string; count: number }[];
|
||||
comment?: string | null;
|
||||
};
|
||||
|
||||
export type NumericAggregate = {
|
||||
export type NumericAggregate = BaseAggregate & {
|
||||
type: "NUMERIC";
|
||||
values: number[];
|
||||
average: number;
|
||||
comment?: string | null;
|
||||
};
|
||||
|
||||
export type ScoreAggregate = Record<
|
||||
@@ -21,11 +25,13 @@ export type ScoreAggregate = Record<
|
||||
>;
|
||||
|
||||
export type ScoreSimplified = {
|
||||
id: string;
|
||||
name: string;
|
||||
dataType: ScoreDataType;
|
||||
source: ScoreSourceType;
|
||||
value?: number | null;
|
||||
comment?: string | null;
|
||||
metadata?: MetadataDomain | null;
|
||||
stringValue?: string | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,12 +38,21 @@ export * from "./features/comments/types";
|
||||
// experiments
|
||||
export * from "./features/experiments/utils";
|
||||
|
||||
// prompts
|
||||
export * from "./features/prompts/parsePromptDependencyTags";
|
||||
|
||||
// export db types only
|
||||
export * from "@prisma/client";
|
||||
export { type DB } from "../prisma/generated/types";
|
||||
export * from "./server/repositories/types";
|
||||
|
||||
// metadata conversion
|
||||
export * from "./server/utils/metadata_conversion";
|
||||
|
||||
// errors
|
||||
export * from "./errors/index";
|
||||
|
||||
export * from "./utils/environment";
|
||||
|
||||
// domain
|
||||
export * from "./domain";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { CloudConfigRateLimit } from "./rate-limits";
|
||||
import { cloudConfigPlans } from "../features/entitlements/plans";
|
||||
|
||||
export const CloudConfigSchema = z.object({
|
||||
plan: z.enum(["Hobby", "Pro", "Team", "Enterprise"]).optional(),
|
||||
plan: z.enum(cloudConfigPlans).optional(),
|
||||
monthlyObservationLimit: z.number().int().positive().optional(),
|
||||
// used for table and dashboard queries
|
||||
defaultLookBackDays: z.number().int().positive().optional(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ObservationLevelType } from "./domain";
|
||||
import {
|
||||
type OptionsDefinition,
|
||||
type ColumnDefinition,
|
||||
} from "./tableDefinitions";
|
||||
import { ObservationLevelType } from "./server";
|
||||
|
||||
// to be used server side
|
||||
export const observationsTableCols: ColumnDefinition[] = [
|
||||
@@ -154,8 +154,8 @@ export const observationsTableCols: ColumnDefinition[] = [
|
||||
nullable: true,
|
||||
},
|
||||
{
|
||||
name: "Usage",
|
||||
id: "usage",
|
||||
name: "Tokens",
|
||||
id: "tokens",
|
||||
type: "number",
|
||||
internal: 'o."total_tokens"',
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaClient, ApiKeyScope } from "@prisma/client";
|
||||
import { compare, hash } from "bcryptjs";
|
||||
import { randomUUID } from "crypto";
|
||||
import * as crypto from "crypto";
|
||||
@@ -38,7 +38,8 @@ export function createShaHash(privateKey: string, salt: string): string {
|
||||
|
||||
export async function createAndAddApiKeysToDb(p: {
|
||||
prisma: PrismaClient;
|
||||
projectId: string;
|
||||
entityId: string;
|
||||
scope: ApiKeyScope;
|
||||
note?: string;
|
||||
predefinedKeys?: {
|
||||
secretKey: string;
|
||||
@@ -59,14 +60,18 @@ export async function createAndAddApiKeysToDb(p: {
|
||||
|
||||
const hashFromProvidedKey = createShaHash(sk, salt);
|
||||
|
||||
const entity =
|
||||
p.scope === "PROJECT" ? { projectId: p.entityId } : { orgId: p.entityId };
|
||||
|
||||
const apiKey = await p.prisma.apiKey.create({
|
||||
data: {
|
||||
projectId: p.projectId,
|
||||
...entity,
|
||||
publicKey: pk,
|
||||
hashedSecretKey: hashedSk,
|
||||
displaySecretKey: displaySk,
|
||||
fastHashedSecretKey: hashFromProvidedKey,
|
||||
note: p.note,
|
||||
scope: p.scope,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers/oauth";
|
||||
import { env } from "../../env";
|
||||
|
||||
const CUSTOM_EMAIL_CLAIM = env.LANGFUSE_CUSTOM_SSO_EMAIL_CLAIM;
|
||||
const CUSTOM_NAME_CLAIM = env.LANGFUSE_CUSTOM_SSO_NAME_CLAIM;
|
||||
const CUSTOM_SUB_CLAIM = env.LANGFUSE_CUSTOM_SSO_SUB_CLAIM;
|
||||
|
||||
interface CustomSSOUser extends Record<string, any> {
|
||||
email: string;
|
||||
@@ -7,6 +12,7 @@ interface CustomSSOUser extends Record<string, any> {
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
|
||||
export function CustomSSOProvider<P extends CustomSSOUser>(
|
||||
options: OAuthUserConfig<P>
|
||||
): OAuthConfig<P> {
|
||||
@@ -19,9 +25,9 @@ export function CustomSSOProvider<P extends CustomSSOUser>(
|
||||
checks: ["pkce", "state"],
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name,
|
||||
email: profile.email,
|
||||
id: profile[CUSTOM_SUB_CLAIM],
|
||||
name: profile[CUSTOM_NAME_CLAIM],
|
||||
email: profile[CUSTOM_EMAIL_CLAIM],
|
||||
image: null,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import z from "zod";
|
||||
import { Plan, plans } from "../../features/entitlements/plans";
|
||||
import { CloudConfigRateLimit } from "../../interfaces/rate-limits";
|
||||
import { ApiKeyScope } from "../../";
|
||||
|
||||
export const OrgEnrichedApiKey = z.object({
|
||||
const ApiKeyBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
note: z.string().nullable(),
|
||||
publicKey: z.string(),
|
||||
@@ -10,7 +11,6 @@ export const OrgEnrichedApiKey = z.object({
|
||||
createdAt: z.string().datetime().nullable(),
|
||||
lastUsedAt: z.string().datetime().nullable(),
|
||||
expiresAt: z.string().datetime().nullable(),
|
||||
projectId: z.string(),
|
||||
fastHashedSecretKey: z.string(),
|
||||
hashedSecretKey: z.string(),
|
||||
orgId: z.string(),
|
||||
@@ -18,6 +18,17 @@ export const OrgEnrichedApiKey = z.object({
|
||||
rateLimitOverrides: CloudConfigRateLimit.nullish(),
|
||||
});
|
||||
|
||||
export const OrgEnrichedApiKey = z.discriminatedUnion("scope", [
|
||||
ApiKeyBaseSchema.extend({
|
||||
scope: z.literal(ApiKeyScope.ORGANIZATION),
|
||||
projectId: z.null(),
|
||||
}),
|
||||
ApiKeyBaseSchema.extend({
|
||||
scope: z.literal(ApiKeyScope.PROJECT),
|
||||
projectId: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const API_KEY_NON_EXISTENT = "api-key-non-existent";
|
||||
|
||||
export const CachedApiKey = z.union([
|
||||
@@ -38,9 +49,10 @@ export type AuthHeaderValidVerificationResult = {
|
||||
};
|
||||
|
||||
export type ApiAccessScope = {
|
||||
projectId: string;
|
||||
accessLevel: "all" | "scores";
|
||||
projectId: string | null;
|
||||
accessLevel: "organization" | "project" | "scores";
|
||||
orgId: string;
|
||||
plan: Plan;
|
||||
rateLimitOverrides: z.infer<typeof CloudConfigRateLimit>;
|
||||
apiKeyId: string;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,15 @@ export const clickhouseClient = (
|
||||
propagation.inject(context.active(), headers);
|
||||
}
|
||||
|
||||
const cloudOptions: Record<string, unknown> = {};
|
||||
if (
|
||||
["STAGING", "EU", "US"].includes(
|
||||
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION ?? "",
|
||||
)
|
||||
) {
|
||||
cloudOptions.input_format_json_throw_on_bad_escape_sequence = 0;
|
||||
}
|
||||
|
||||
return createClient({
|
||||
...params.opts,
|
||||
url: env.CLICKHOUSE_URL,
|
||||
@@ -26,6 +35,8 @@ export const clickhouseClient = (
|
||||
database: env.CLICKHOUSE_DB,
|
||||
http_headers: headers,
|
||||
clickhouse_settings: {
|
||||
...cloudOptions,
|
||||
...(params.opts?.clickhouse_settings ?? {}),
|
||||
log_comment: JSON.stringify(params.tags ?? {}),
|
||||
async_insert: 1,
|
||||
wait_for_async_insert: 1, // if disabled, we won't get errors from clickhouse
|
||||
|
||||
@@ -2,6 +2,11 @@ export const ClickhouseTableNames = {
|
||||
traces: "traces",
|
||||
observations: "observations",
|
||||
scores: "scores",
|
||||
|
||||
// Virtual tables for dashboards
|
||||
// TODO: Check if we can do this more elegantly
|
||||
scores_numeric: "scores_numeric",
|
||||
scores_categorical: "scores_categorical",
|
||||
} as const;
|
||||
|
||||
export type ClickhouseTableName = keyof typeof ClickhouseTableNames;
|
||||
|
||||
@@ -17,7 +17,6 @@ export * from "./clickhouse/schemaUtils";
|
||||
export * from "./clickhouse/schema";
|
||||
export * from "./repositories/definitions";
|
||||
export * from "../server/ingestion/types";
|
||||
export * from "./ingestion/modelMatch";
|
||||
export * from "./ingestion/processEventBatch";
|
||||
export * from "../server/ingestion/types";
|
||||
export * from "../server/ingestion/validateAndInflateScore";
|
||||
@@ -28,12 +27,15 @@ export * from "./redis/cloudUsageMeteringQueue";
|
||||
export * from "./redis/getQueue";
|
||||
export * from "./redis/traceDelete";
|
||||
export * from "./redis/projectDelete";
|
||||
export * from "./redis/scoreDelete";
|
||||
export * from "./redis/datasetRunItemUpsert";
|
||||
export * from "./redis/batchExport";
|
||||
export * from "./redis/batchActionQueue";
|
||||
export * from "./redis/ingestionQueue";
|
||||
export * from "./redis/postHogIntegrationQueue";
|
||||
export * from "./redis/postHogIntegrationProcessingQueue";
|
||||
export * from "./redis/blobStorageIntegrationQueue";
|
||||
export * from "./redis/blobStorageIntegrationProcessingQueue";
|
||||
export * from "./redis/dataRetentionQueue";
|
||||
export * from "./redis/dataRetentionProcessingQueue";
|
||||
export * from "./redis/coreDataS3ExportQueue";
|
||||
@@ -49,6 +51,8 @@ export * from "./queries";
|
||||
export * from "./repositories";
|
||||
export * from "./redis/evalExecutionQueue";
|
||||
export * from "./services/sessions-ui-table-service";
|
||||
export * from "./services/datasets-ui-table-service";
|
||||
export * from "./services/DashboardService";
|
||||
|
||||
// test utils
|
||||
export * from "./test-utils";
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Model, Prisma } from "@prisma/client";
|
||||
import { prisma } from "../../db";
|
||||
import { Observation } from "../repositories";
|
||||
|
||||
export async function findModel(p: {
|
||||
event: {
|
||||
projectId: string;
|
||||
model?: string;
|
||||
unit?: string;
|
||||
startTime?: Date;
|
||||
};
|
||||
existingDbObservation?: Omit<Observation, "input" | "output">;
|
||||
}): Promise<Model | null> {
|
||||
const { event, existingDbObservation } = p;
|
||||
// either get the model from the existing observation
|
||||
// or match pattern on the user provided model name
|
||||
const modelCondition = event.model
|
||||
? Prisma.sql`AND ${event.model} ~ match_pattern`
|
||||
: existingDbObservation?.internalModel
|
||||
? Prisma.sql`AND model_name = ${existingDbObservation.internalModel}`
|
||||
: undefined;
|
||||
if (!modelCondition) return null;
|
||||
|
||||
// unit based on the current event or the existing observation, both can be undefined
|
||||
const mergedUnit = event.unit ?? existingDbObservation?.unit;
|
||||
|
||||
const unitCondition = mergedUnit
|
||||
? Prisma.sql`AND unit = ${mergedUnit}`
|
||||
: Prisma.empty;
|
||||
|
||||
const sql = Prisma.sql`
|
||||
SELECT
|
||||
id,
|
||||
created_at AS "createdAt",
|
||||
updated_at AS "updatedAt",
|
||||
project_id AS "projectId",
|
||||
model_name AS "modelName",
|
||||
match_pattern AS "matchPattern",
|
||||
start_date AS "startDate",
|
||||
input_price AS "inputPrice",
|
||||
output_price AS "outputPrice",
|
||||
total_price AS "totalPrice",
|
||||
unit,
|
||||
tokenizer_id AS "tokenizerId",
|
||||
tokenizer_config AS "tokenizerConfig"
|
||||
FROM
|
||||
models
|
||||
WHERE (project_id = ${event.projectId}
|
||||
OR project_id IS NULL)
|
||||
${modelCondition}
|
||||
${unitCondition}
|
||||
AND (start_date IS NULL OR start_date <= ${
|
||||
event.startTime ? new Date(event.startTime) : new Date()
|
||||
}::timestamp with time zone at time zone 'UTC')
|
||||
ORDER BY
|
||||
project_id ASC,
|
||||
start_date DESC NULLS LAST
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const foundModels = await prisma.$queryRaw<Array<Model>>(sql);
|
||||
|
||||
return foundModels[0] ?? null;
|
||||
}
|
||||
@@ -98,13 +98,20 @@ export const processEventBatch = async (
|
||||
recordDistribution("langfuse.ingestion.event_distribution", input.length);
|
||||
|
||||
currentSpan?.setAttribute("langfuse.ingestion.batch_size", input.length);
|
||||
currentSpan?.setAttribute("langfuse.project.id", authCheck.scope.projectId);
|
||||
currentSpan?.setAttribute(
|
||||
"langfuse.project.id",
|
||||
authCheck.scope.projectId ?? "",
|
||||
);
|
||||
currentSpan?.setAttribute("langfuse.org.id", authCheck.scope.orgId);
|
||||
currentSpan?.setAttribute("langfuse.org.plan", authCheck.scope.plan);
|
||||
|
||||
/**************
|
||||
* VALIDATION *
|
||||
**************/
|
||||
if (!authCheck.scope.projectId) {
|
||||
throw new UnauthorizedError("Missing project ID");
|
||||
}
|
||||
|
||||
const validationErrors: { id: string; error: unknown }[] = [];
|
||||
const authenticationErrors: { id: string; error: unknown }[] = [];
|
||||
|
||||
@@ -245,7 +252,13 @@ export const processEventBatch = async (
|
||||
eventBodyId: sortedBatchByEventBodyId[id].eventBodyId,
|
||||
fileKey: sortedBatchByEventBodyId[id].key,
|
||||
},
|
||||
authCheck,
|
||||
authCheck: authCheck as {
|
||||
validKey: true;
|
||||
scope: {
|
||||
projectId: string;
|
||||
accessLevel: "project" | "scores";
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
{ delay: getDelay(delay) },
|
||||
@@ -272,11 +285,11 @@ const isAuthorized = (
|
||||
if (event.type === eventTypes.SCORE_CREATE) {
|
||||
return (
|
||||
authScope.scope.accessLevel === "scores" ||
|
||||
authScope.scope.accessLevel === "all"
|
||||
authScope.scope.accessLevel === "project"
|
||||
);
|
||||
}
|
||||
|
||||
return authScope.scope.accessLevel === "all";
|
||||
return authScope.scope.accessLevel === "project";
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,15 @@ import { z } from "zod";
|
||||
|
||||
import { NonEmptyString, jsonSchema } from "../../utils/zod";
|
||||
import { ModelUsageUnit } from "../../constants";
|
||||
import { type ScoreSourceType } from "../repositories";
|
||||
import { ScoreSourceType } from "../../domain";
|
||||
|
||||
export const idSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(800) // AWS S3 allows for 1024 bytes for object keys and we need enough room to construct the entire key/path. https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
|
||||
.refine((id) => !id.includes("\r"), {
|
||||
message: "ID cannot contain carriage return characters",
|
||||
});
|
||||
|
||||
const ObservationLevel = z.enum(["DEBUG", "DEFAULT", "WARNING", "ERROR"]);
|
||||
|
||||
@@ -58,21 +66,25 @@ export const usage = MixedUsage.nullish()
|
||||
// ensure output is always of new usage model
|
||||
.pipe(Usage.nullish());
|
||||
|
||||
const RawUsageOrCostDetails = z.record(
|
||||
const CostDetails = z
|
||||
.record(z.string(), z.number().nonnegative().nullish())
|
||||
.nullish();
|
||||
|
||||
const RawUsageDetails = z.record(
|
||||
z.string(),
|
||||
z.number().nonnegative().nullish(),
|
||||
z.number().int().nonnegative().nullish(),
|
||||
);
|
||||
|
||||
const OpenAIUsageSchema = z
|
||||
const OpenAICompletionUsageSchema = z
|
||||
.object({
|
||||
prompt_tokens: z.number().nonnegative(),
|
||||
completion_tokens: z.number().nonnegative(),
|
||||
total_tokens: z.number().nonnegative(),
|
||||
prompt_tokens: z.number().int().nonnegative(),
|
||||
completion_tokens: z.number().int().nonnegative(),
|
||||
total_tokens: z.number().int().nonnegative(),
|
||||
prompt_tokens_details: z
|
||||
.record(z.string(), z.number().nonnegative())
|
||||
.record(z.string(), z.number().int().nonnegative().nullish())
|
||||
.nullish(),
|
||||
completion_tokens_details: z
|
||||
.record(z.string(), z.number().nonnegative())
|
||||
.record(z.string(), z.number().int().nonnegative().nullish())
|
||||
.nullish(),
|
||||
})
|
||||
.strict()
|
||||
@@ -86,7 +98,7 @@ const OpenAIUsageSchema = z
|
||||
prompt_tokens_details,
|
||||
completion_tokens_details,
|
||||
} = v;
|
||||
const result: z.infer<typeof RawUsageOrCostDetails> & {
|
||||
const result: z.infer<typeof RawUsageDetails> & {
|
||||
input: number;
|
||||
output: number;
|
||||
total: number;
|
||||
@@ -98,24 +110,88 @@ const OpenAIUsageSchema = z
|
||||
|
||||
if (prompt_tokens_details) {
|
||||
for (const [key, value] of Object.entries(prompt_tokens_details)) {
|
||||
result[`input_${key}`] = value;
|
||||
result.input = Math.max(result.input - (value ?? 0), 0);
|
||||
if (value !== null && value !== undefined) {
|
||||
result[`input_${key}`] = value;
|
||||
result.input = Math.max(result.input - (value ?? 0), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (completion_tokens_details) {
|
||||
for (const [key, value] of Object.entries(completion_tokens_details)) {
|
||||
result[`output_${key}`] = value;
|
||||
result.output = Math.max(result.output - (value ?? 0), 0);
|
||||
if (value !== null && value !== undefined) {
|
||||
result[`output_${key}`] = value;
|
||||
result.output = Math.max(result.output - (value ?? 0), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.pipe(RawUsageOrCostDetails);
|
||||
.pipe(RawUsageDetails);
|
||||
|
||||
export const UsageOrCostDetails = z
|
||||
.union([OpenAIUsageSchema, RawUsageOrCostDetails])
|
||||
// The new OpenAI Response API uses a new Usage schema that departs from the Completion API Usage schema
|
||||
const OpenAIResponseUsageSchema = z
|
||||
.object({
|
||||
input_tokens: z.number().int().nonnegative(),
|
||||
output_tokens: z.number().int().nonnegative(),
|
||||
total_tokens: z.number().int().nonnegative(),
|
||||
input_tokens_details: z
|
||||
.record(z.string(), z.number().int().nonnegative().nullish())
|
||||
.nullish(),
|
||||
output_tokens_details: z
|
||||
.record(z.string(), z.number().int().nonnegative().nullish())
|
||||
.nullish(),
|
||||
})
|
||||
.strict()
|
||||
.transform((v) => {
|
||||
if (!v) return;
|
||||
|
||||
const {
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
total_tokens,
|
||||
input_tokens_details,
|
||||
output_tokens_details,
|
||||
} = v;
|
||||
const result: z.infer<typeof RawUsageDetails> & {
|
||||
input: number;
|
||||
output: number;
|
||||
total: number;
|
||||
} = {
|
||||
input: input_tokens,
|
||||
output: output_tokens,
|
||||
total: total_tokens,
|
||||
};
|
||||
|
||||
if (input_tokens_details) {
|
||||
for (const [key, value] of Object.entries(input_tokens_details)) {
|
||||
if (value !== null && value !== undefined) {
|
||||
result[`input_${key}`] = value;
|
||||
result.input = Math.max(result.input - (value ?? 0), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (output_tokens_details) {
|
||||
for (const [key, value] of Object.entries(output_tokens_details)) {
|
||||
if (value !== null && value !== undefined) {
|
||||
result[`output_${key}`] = value;
|
||||
result.output = Math.max(result.output - (value ?? 0), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.pipe(RawUsageDetails);
|
||||
|
||||
export const UsageDetails = z
|
||||
.union([
|
||||
OpenAICompletionUsageSchema,
|
||||
OpenAIResponseUsageSchema,
|
||||
RawUsageDetails,
|
||||
])
|
||||
.nullish();
|
||||
|
||||
export const EnvironmentName = z
|
||||
@@ -131,7 +207,7 @@ export const EnvironmentName = z
|
||||
// With this setup parsing should be more lightweight and doesn't block other requests.
|
||||
// As we allow plain values, arrays, and objects the JSON parse via bodyParser should suffice.
|
||||
export const TraceBody = z.object({
|
||||
id: z.string().nullish(),
|
||||
id: idSchema.nullish(),
|
||||
timestamp: stringDateTime,
|
||||
name: z.string().max(1000).nullish(),
|
||||
externalId: z.string().nullish(),
|
||||
@@ -148,7 +224,7 @@ export const TraceBody = z.object({
|
||||
});
|
||||
|
||||
export const OptionalObservationBody = z.object({
|
||||
traceId: z.string().nullish(),
|
||||
traceId: idSchema.nullish(),
|
||||
environment: EnvironmentName,
|
||||
name: z.string().nullish(),
|
||||
startTime: stringDateTime,
|
||||
@@ -162,11 +238,11 @@ export const OptionalObservationBody = z.object({
|
||||
});
|
||||
|
||||
export const CreateEventEvent = OptionalObservationBody.extend({
|
||||
id: NonEmptyString,
|
||||
id: idSchema,
|
||||
});
|
||||
|
||||
export const UpdateEventEvent = OptionalObservationBody.extend({
|
||||
id: NonEmptyString,
|
||||
id: idSchema,
|
||||
});
|
||||
|
||||
export const CreateSpanBody = CreateEventEvent.extend({
|
||||
@@ -195,8 +271,8 @@ export const CreateGenerationBody = CreateSpanBody.extend({
|
||||
)
|
||||
.nullish(),
|
||||
usage: usage,
|
||||
usageDetails: UsageOrCostDetails,
|
||||
costDetails: UsageOrCostDetails,
|
||||
usageDetails: UsageDetails,
|
||||
costDetails: CostDetails,
|
||||
promptName: z.string().nullish(),
|
||||
promptVersion: z.number().int().nullish(),
|
||||
}).refine((value) => {
|
||||
@@ -225,8 +301,8 @@ export const UpdateGenerationBody = UpdateSpanBody.extend({
|
||||
)
|
||||
.nullish(),
|
||||
usage: usage,
|
||||
usageDetails: UsageOrCostDetails,
|
||||
costDetails: UsageOrCostDetails,
|
||||
usageDetails: UsageDetails,
|
||||
costDetails: CostDetails,
|
||||
promptName: z.string().nullish(),
|
||||
promptVersion: z.number().int().nullish(),
|
||||
}).refine((value) => {
|
||||
@@ -238,12 +314,13 @@ export const UpdateGenerationBody = UpdateSpanBody.extend({
|
||||
});
|
||||
|
||||
const BaseScoreBody = z.object({
|
||||
id: z.string().nullish(),
|
||||
id: idSchema.nullish(),
|
||||
name: NonEmptyString,
|
||||
traceId: z.string(),
|
||||
environment: EnvironmentName,
|
||||
observationId: z.string().nullish(),
|
||||
comment: z.string().nullish(),
|
||||
metadata: jsonSchema.nullish(),
|
||||
source: z
|
||||
.enum(["API", "EVAL", "ANNOTATION"])
|
||||
.default("API" as ScoreSourceType),
|
||||
@@ -288,8 +365,8 @@ export const ScoreBody = z.discriminatedUnion("dataType", [
|
||||
|
||||
// LEGACY, only required for backwards compatibility
|
||||
export const LegacySpanPostSchema = z.object({
|
||||
id: z.string().nullish(),
|
||||
traceId: z.string().nullish(),
|
||||
id: idSchema.nullish(),
|
||||
traceId: idSchema.nullish(),
|
||||
name: z.string().nullish(),
|
||||
startTime: stringDateTime,
|
||||
endTime: stringDateTime,
|
||||
@@ -303,8 +380,8 @@ export const LegacySpanPostSchema = z.object({
|
||||
});
|
||||
|
||||
export const LegacySpanPatchSchema = z.object({
|
||||
spanId: z.string(),
|
||||
traceId: z.string().nullish(),
|
||||
spanId: idSchema,
|
||||
traceId: idSchema.nullish(),
|
||||
name: z.string().nullish(),
|
||||
startTime: stringDateTime,
|
||||
endTime: stringDateTime,
|
||||
@@ -317,8 +394,8 @@ export const LegacySpanPatchSchema = z.object({
|
||||
});
|
||||
|
||||
export const LegacyGenerationsCreateSchema = z.object({
|
||||
id: z.string().nullish(),
|
||||
traceId: z.string().nullish(),
|
||||
id: idSchema.nullish(),
|
||||
traceId: idSchema.nullish(),
|
||||
name: z.string().nullish(),
|
||||
startTime: stringDateTime,
|
||||
endTime: stringDateTime,
|
||||
@@ -341,8 +418,8 @@ export const LegacyGenerationsCreateSchema = z.object({
|
||||
});
|
||||
|
||||
export const LegacyGenerationPatchSchema = z.object({
|
||||
generationId: z.string(),
|
||||
traceId: z.string().nullish(),
|
||||
generationId: idSchema,
|
||||
traceId: idSchema.nullish(),
|
||||
name: z.string().nullish(),
|
||||
startTime: stringDateTime,
|
||||
endTime: stringDateTime,
|
||||
@@ -364,8 +441,8 @@ export const LegacyGenerationPatchSchema = z.object({
|
||||
});
|
||||
|
||||
export const LegacyObservationBody = z.object({
|
||||
id: z.string().nullish(),
|
||||
traceId: z.string().nullish(),
|
||||
id: idSchema.nullish(),
|
||||
traceId: idSchema.nullish(),
|
||||
type: z.enum(["GENERATION", "SPAN", "EVENT"]),
|
||||
name: z.string().nullish(),
|
||||
startTime: stringDateTime,
|
||||
@@ -381,8 +458,8 @@ export const LegacyObservationBody = z.object({
|
||||
input: jsonSchema.nullish(),
|
||||
output: jsonSchema.nullish(),
|
||||
usage: usage,
|
||||
usageDetails: UsageOrCostDetails,
|
||||
costDetails: UsageOrCostDetails,
|
||||
usageDetails: UsageDetails,
|
||||
costDetails: CostDetails,
|
||||
metadata: jsonSchema.nullish(),
|
||||
parentObservationId: z.string().nullish(),
|
||||
level: ObservationLevel.nullish(),
|
||||
@@ -410,7 +487,7 @@ export const eventTypes = {
|
||||
} as const;
|
||||
|
||||
const base = z.object({
|
||||
id: z.string(),
|
||||
id: idSchema,
|
||||
timestamp: z.string().datetime({ offset: true }),
|
||||
metadata: jsonSchema.nullish(),
|
||||
});
|
||||
@@ -473,13 +550,6 @@ export const ingestionEvent = z.discriminatedUnion("type", [
|
||||
]);
|
||||
export type IngestionEventType = z.infer<typeof ingestionEvent>;
|
||||
|
||||
export const ingestionBatchEvent = z.array(ingestionEvent);
|
||||
|
||||
export const ingestionApiSchema = z.object({
|
||||
batch: ingestionBatchEvent,
|
||||
metadata: jsonSchema.nullish(),
|
||||
});
|
||||
|
||||
export type ObservationEvent =
|
||||
| z.infer<typeof legacyObservationCreateEvent>
|
||||
| z.infer<typeof legacyObservationUpdateEvent>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
ScoreBodyWithoutConfig,
|
||||
ScoreDomain,
|
||||
ScorePropsAgainstConfig,
|
||||
validateDbScoreConfigSafe,
|
||||
ValidatedScoreConfig,
|
||||
} from "../../../src";
|
||||
import { prisma, ScoreDataType } from "../../db";
|
||||
import { Score } from "../repositories";
|
||||
|
||||
import { InvalidRequestError, LangfuseNotFoundError } from "../../errors";
|
||||
|
||||
@@ -17,7 +17,7 @@ type ValidateAndInflateScoreParams = {
|
||||
|
||||
export async function validateAndInflateScore(
|
||||
params: ValidateAndInflateScoreParams,
|
||||
): Promise<Score> {
|
||||
): Promise<ScoreDomain> {
|
||||
const { body, projectId, scoreId } = params;
|
||||
|
||||
if (body.configId) {
|
||||
@@ -85,7 +85,7 @@ function mapStringValueToNumericValue(
|
||||
|
||||
function inflateScoreBody(
|
||||
params: ValidateAndInflateScoreParams & { config?: ValidatedScoreConfig },
|
||||
): Score {
|
||||
): ScoreDomain {
|
||||
const { body, projectId, scoreId, config } = params;
|
||||
|
||||
const relevantDataType = config?.dataType ?? body.dataType;
|
||||
|
||||
@@ -160,35 +160,49 @@ export const addUserToSpan = (
|
||||
export const getTracer = (name: string) => opentelemetry.trace.getTracer(name);
|
||||
|
||||
const cloudWatchClient = new CloudWatchClient();
|
||||
const cloudWatchLastSubmitted: Record<string, number> = {};
|
||||
const sendCloudWatchMetric = (key: string, value: number | undefined) => {
|
||||
const currentTime = Date.now();
|
||||
const interval = 30 * 1000;
|
||||
let lastFlushTime = 0;
|
||||
let metricCache: Record<string, number> = {};
|
||||
|
||||
// Check if the function has been executed in the last 30s for this key
|
||||
if (
|
||||
!cloudWatchLastSubmitted[key] ||
|
||||
currentTime - cloudWatchLastSubmitted[key] >= interval
|
||||
) {
|
||||
cloudWatchLastSubmitted[key] = currentTime;
|
||||
cloudWatchClient
|
||||
.send(
|
||||
new PutMetricDataCommand({
|
||||
Namespace: "Langfuse",
|
||||
MetricData: [
|
||||
{
|
||||
MetricName: key,
|
||||
Value: value ?? 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
logger.warn("Failed to send metric to CloudWatch", error);
|
||||
});
|
||||
// Caches metrics and flushes them on schedule
|
||||
const sendCloudWatchMetric = (key: string, value: number, replace: boolean) => {
|
||||
// Store the latest value for each metric key. If replace is false (e.g. for increments) we add the value to the existing value.
|
||||
metricCache[key] = replace ? value : (metricCache[key] || 0) + value;
|
||||
|
||||
const currentTime = Date.now();
|
||||
const flushInterval = 30 * 1000; // 30 seconds
|
||||
|
||||
// Check if it's time to flush the metrics
|
||||
if (currentTime - lastFlushTime >= flushInterval) {
|
||||
flushMetricsToCloudWatch();
|
||||
}
|
||||
};
|
||||
|
||||
// Flush all cached metrics in a single API call
|
||||
const flushMetricsToCloudWatch = () => {
|
||||
if (Object.keys(metricCache).length === 0) return;
|
||||
|
||||
lastFlushTime = Date.now();
|
||||
|
||||
const metricData = Object.entries(metricCache).map(([key, value]) => ({
|
||||
MetricName: key,
|
||||
Value: value,
|
||||
}));
|
||||
|
||||
// Clear the cache after preparing the metrics
|
||||
metricCache = {};
|
||||
|
||||
cloudWatchClient
|
||||
.send(
|
||||
new PutMetricDataCommand({
|
||||
Namespace: "Langfuse",
|
||||
MetricData: metricData,
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
logger.warn("Failed to send metrics to CloudWatch", error);
|
||||
});
|
||||
};
|
||||
|
||||
export const recordGauge = (
|
||||
stat: string,
|
||||
value?: number | undefined,
|
||||
@@ -199,7 +213,7 @@ export const recordGauge = (
|
||||
| undefined,
|
||||
) => {
|
||||
if (env.ENABLE_AWS_CLOUDWATCH_METRIC_PUBLISHING === "true") {
|
||||
sendCloudWatchMetric(stat, value);
|
||||
sendCloudWatchMetric(stat, value ?? 0, true);
|
||||
}
|
||||
dd.dogstatsd.gauge(stat, value, tags);
|
||||
};
|
||||
@@ -210,7 +224,7 @@ export const recordIncrement = (
|
||||
tags?: { [tag: string]: string | number } | undefined,
|
||||
) => {
|
||||
if (env.ENABLE_AWS_CLOUDWATCH_METRIC_PUBLISHING === "true") {
|
||||
sendCloudWatchMetric(stat, value);
|
||||
sendCloudWatchMetric(stat, value ?? 1, false);
|
||||
}
|
||||
dd.dogstatsd.increment(stat, value, tags);
|
||||
};
|
||||
@@ -220,9 +234,6 @@ export const recordHistogram = (
|
||||
value?: number | undefined,
|
||||
tags?: { [tag: string]: string | number } | undefined,
|
||||
) => {
|
||||
if (env.ENABLE_AWS_CLOUDWATCH_METRIC_PUBLISHING === "true") {
|
||||
sendCloudWatchMetric(stat, value);
|
||||
}
|
||||
dd.dogstatsd.histogram(stat, value, tags);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ZodSchema } from "zod";
|
||||
import { type ZodSchema } from "zod";
|
||||
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { ChatVertexAI } from "@langchain/google-vertexai";
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
BaseMessage,
|
||||
HumanMessage,
|
||||
SystemMessage,
|
||||
ToolMessage,
|
||||
} from "@langchain/core/messages";
|
||||
import {
|
||||
BytesOutputParser,
|
||||
@@ -25,8 +26,13 @@ import { logger } from "../logger";
|
||||
import {
|
||||
ChatMessage,
|
||||
ChatMessageRole,
|
||||
ChatMessageType,
|
||||
LLMAdapter,
|
||||
LLMJSONSchema,
|
||||
LLMToolDefinition,
|
||||
ModelParams,
|
||||
ToolCallResponse,
|
||||
ToolCallResponseSchema,
|
||||
TraceParams,
|
||||
} from "./types";
|
||||
import { CallbackHandler } from "langfuse-langchain";
|
||||
@@ -37,7 +43,7 @@ type ProcessTracedEvents = () => Promise<void>;
|
||||
type LLMCompletionParams = {
|
||||
messages: ChatMessage[];
|
||||
modelParams: ModelParams;
|
||||
structuredOutputSchema?: ZodSchema;
|
||||
structuredOutputSchema?: ZodSchema | LLMJSONSchema;
|
||||
callbacks?: BaseCallbackHandler[];
|
||||
baseURL?: string;
|
||||
apiKey: string;
|
||||
@@ -50,6 +56,7 @@ type LLMCompletionParams = {
|
||||
|
||||
type FetchLLMCompletionParams = LLMCompletionParams & {
|
||||
streaming: boolean;
|
||||
tools?: LLMToolDefinition[];
|
||||
};
|
||||
|
||||
export async function fetchLLMCompletion(
|
||||
@@ -65,7 +72,10 @@ export async function fetchLLMCompletion(
|
||||
params: LLMCompletionParams & {
|
||||
streaming: false;
|
||||
},
|
||||
): Promise<{ completion: string; processTracedEvents: ProcessTracedEvents }>;
|
||||
): Promise<{
|
||||
completion: string;
|
||||
processTracedEvents: ProcessTracedEvents;
|
||||
}>;
|
||||
|
||||
export async function fetchLLMCompletion(
|
||||
params: LLMCompletionParams & {
|
||||
@@ -73,19 +83,34 @@ export async function fetchLLMCompletion(
|
||||
structuredOutputSchema: ZodSchema;
|
||||
},
|
||||
): Promise<{
|
||||
completion: unknown;
|
||||
completion: Record<string, unknown>;
|
||||
processTracedEvents: ProcessTracedEvents;
|
||||
}>;
|
||||
|
||||
export async function fetchLLMCompletion(
|
||||
params: LLMCompletionParams & {
|
||||
tools: LLMToolDefinition[];
|
||||
streaming: false;
|
||||
},
|
||||
): Promise<{
|
||||
completion: ToolCallResponse;
|
||||
processTracedEvents: ProcessTracedEvents;
|
||||
}>;
|
||||
|
||||
export async function fetchLLMCompletion(
|
||||
params: FetchLLMCompletionParams,
|
||||
): Promise<{
|
||||
completion: string | IterableReadableStream<Uint8Array> | unknown;
|
||||
completion:
|
||||
| string
|
||||
| IterableReadableStream<Uint8Array>
|
||||
| Record<string, unknown>
|
||||
| ToolCallResponse;
|
||||
processTracedEvents: ProcessTracedEvents;
|
||||
}> {
|
||||
// the apiKey must never be printed to the console
|
||||
const {
|
||||
messages,
|
||||
tools,
|
||||
modelParams,
|
||||
streaming,
|
||||
callbacks,
|
||||
@@ -140,11 +165,25 @@ export async function fetchLLMCompletion(
|
||||
)
|
||||
return new SystemMessage(message.content);
|
||||
|
||||
return new AIMessage(message.content);
|
||||
if (message.type === ChatMessageType.ToolResult)
|
||||
return new ToolMessage({
|
||||
content: message.content,
|
||||
tool_call_id: message.toolCallId,
|
||||
});
|
||||
|
||||
return new AIMessage({
|
||||
content: message.content,
|
||||
tool_calls:
|
||||
message.type === ChatMessageType.AssistantToolCall
|
||||
? (message.toolCalls as any)
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
finalMessages = finalMessages.filter((m) => m.content.length > 0);
|
||||
finalMessages = finalMessages.filter(
|
||||
(m) => m.content.length > 0 || "tool_calls" in m,
|
||||
);
|
||||
|
||||
let chatModel:
|
||||
| ChatOpenAI
|
||||
@@ -184,13 +223,16 @@ export async function fetchLLMCompletion(
|
||||
azureOpenAIApiKey: apiKey,
|
||||
azureOpenAIBasePath: baseURL,
|
||||
azureOpenAIApiDeploymentName: modelParams.model,
|
||||
azureOpenAIApiVersion: "2024-02-01",
|
||||
azureOpenAIApiVersion: "2025-02-01-preview",
|
||||
temperature: modelParams.temperature,
|
||||
maxTokens: modelParams.max_tokens,
|
||||
topP: modelParams.top_p,
|
||||
callbacks: finalCallbacks,
|
||||
maxRetries,
|
||||
timeout: 1000 * 60 * 2, // 2 minutes timeout
|
||||
configuration: {
|
||||
defaultHeaders: extraHeaders,
|
||||
},
|
||||
});
|
||||
} else if (modelParams.adapter === LLMAdapter.Bedrock) {
|
||||
const { region } = BedrockConfigSchema.parse(config);
|
||||
@@ -234,10 +276,28 @@ export async function fetchLLMCompletion(
|
||||
maxRetries,
|
||||
apiKey,
|
||||
});
|
||||
} else if (modelParams.adapter === LLMAdapter.Atla) {
|
||||
// Atla models do not support:
|
||||
// - temperature
|
||||
// - max_tokens
|
||||
// - top_p
|
||||
chatModel = new ChatOpenAI({
|
||||
openAIApiKey: apiKey,
|
||||
modelName: modelParams.model,
|
||||
callbacks: finalCallbacks,
|
||||
maxRetries,
|
||||
configuration: {
|
||||
baseURL: baseURL,
|
||||
defaultHeaders: extraHeaders,
|
||||
},
|
||||
timeout: 1000 * 60, // 1 minute timeout
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const _exhaustiveCheck: never = modelParams.adapter;
|
||||
throw new Error("This model provider is not supported.");
|
||||
throw new Error(
|
||||
`This model provider is not supported: ${_exhaustiveCheck}`,
|
||||
);
|
||||
}
|
||||
|
||||
const runConfig = {
|
||||
@@ -301,6 +361,25 @@ export async function fetchLLMCompletion(
|
||||
};
|
||||
}
|
||||
|
||||
if (tools && tools.length > 0) {
|
||||
const langchainTools = tools.map((tool) => ({
|
||||
type: "function",
|
||||
function: tool,
|
||||
}));
|
||||
|
||||
const result = await chatModel
|
||||
.bindTools(langchainTools)
|
||||
.invoke(finalMessages, runConfig);
|
||||
|
||||
const parsed = ToolCallResponseSchema.safeParse(result);
|
||||
if (!parsed.success) throw Error("Failed to parse LLM tool call result");
|
||||
|
||||
return {
|
||||
completion: parsed.data,
|
||||
processTracedEvents,
|
||||
};
|
||||
}
|
||||
|
||||
if (streaming) {
|
||||
return {
|
||||
completion: await chatModel
|
||||
@@ -320,6 +399,7 @@ export async function fetchLLMCompletion(
|
||||
if (throwOnError) {
|
||||
throw error;
|
||||
}
|
||||
return { completion: null, processTracedEvents };
|
||||
|
||||
return { completion: "", processTracedEvents };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,48 +7,212 @@ import { AuthHeaderValidVerificationResult } from "../auth/types";
|
||||
/* eslint-disable no-unused-vars */
|
||||
// disable lint as this is exported and used in web/worker
|
||||
|
||||
export type PromptVariable = { name: string; value: string; isUsed: boolean };
|
||||
export const LLMJSONSchema = z.record(z.string(), z.unknown());
|
||||
export type LLMJSONSchema = z.infer<typeof LLMJSONSchema>;
|
||||
|
||||
export type ChatMessage = {
|
||||
role: ChatMessageRole | string; // Users may ingest any string as role via API/SDK
|
||||
content: string;
|
||||
};
|
||||
export const JSONSchemaFormSchema = z
|
||||
.string()
|
||||
.transform((value, ctx) => {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return parsed;
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Parameters must be valid JSON",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
})
|
||||
.pipe(
|
||||
z
|
||||
.object({
|
||||
type: z.literal("object"),
|
||||
properties: z.record(z.any()),
|
||||
required: z.array(z.string()).optional(),
|
||||
additionalProperties: z.boolean().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
.transform((data) => JSON.stringify(data, null, 2)),
|
||||
);
|
||||
|
||||
export const LLMToolDefinitionSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
parameters: LLMJSONSchema,
|
||||
});
|
||||
export type LLMToolDefinition = z.infer<typeof LLMToolDefinitionSchema>;
|
||||
|
||||
const AnthropicMessageContentWithToolUse = z.union([
|
||||
z.object({
|
||||
type: z.literal("text"),
|
||||
text: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("tool_use"),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
input: z.unknown(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const LLMToolCallSchema = z.object({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
args: z.record(z.string(), z.unknown()),
|
||||
});
|
||||
export type LLMToolCall = z.infer<typeof LLMToolCallSchema>;
|
||||
|
||||
export const OpenAIToolCallSchema = z.object({
|
||||
id: z.string(),
|
||||
function: z.object({
|
||||
name: z.string(),
|
||||
arguments: z.union([
|
||||
z.record(z.string(), z.unknown()),
|
||||
z
|
||||
.string()
|
||||
.transform((v) => JSON.parse(v))
|
||||
.pipe(z.record(z.string(), z.unknown())),
|
||||
]),
|
||||
}),
|
||||
type: z.literal("function"),
|
||||
});
|
||||
export type OpenAIToolCallSchema = z.infer<typeof OpenAIToolCallSchema>;
|
||||
|
||||
export const OpenAIToolSchema = z.object({
|
||||
type: z.literal("function"),
|
||||
function: LLMToolDefinitionSchema,
|
||||
});
|
||||
export type OpenAIToolSchema = z.infer<typeof OpenAIToolSchema>;
|
||||
|
||||
export const OpenAIResponseFormatSchema = z.object({
|
||||
type: z.literal("json_schema"),
|
||||
json_schema: z.object({
|
||||
name: z.string(),
|
||||
schema: LLMJSONSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
export const ToolCallResponseSchema = z.object({
|
||||
content: z.union([z.string(), z.array(AnthropicMessageContentWithToolUse)]),
|
||||
tool_calls: z.array(LLMToolCallSchema),
|
||||
});
|
||||
export type ToolCallResponse = z.infer<typeof ToolCallResponseSchema>;
|
||||
export enum ChatMessageRole {
|
||||
System = "system",
|
||||
Developer = "developer",
|
||||
User = "user",
|
||||
Assistant = "assistant",
|
||||
Tool = "tool",
|
||||
}
|
||||
|
||||
export enum ChatMessageType {
|
||||
System = "system",
|
||||
Developer = "developer",
|
||||
User = "user",
|
||||
AssistantText = "assistant-text",
|
||||
AssistantToolCall = "assistant-tool-call",
|
||||
ToolResult = "tool-result",
|
||||
PublicAPICreated = "public-api-created",
|
||||
}
|
||||
|
||||
export const SystemMessageSchema = z.object({
|
||||
type: z.literal(ChatMessageType.System),
|
||||
role: z.literal(ChatMessageRole.System),
|
||||
content: z.string(),
|
||||
});
|
||||
export type SystemMessage = z.infer<typeof SystemMessageSchema>;
|
||||
|
||||
export const DeveloperMessageSchema = z.object({
|
||||
type: z.literal(ChatMessageType.Developer),
|
||||
role: z.literal(ChatMessageRole.Developer),
|
||||
content: z.string(),
|
||||
});
|
||||
export type DeveloperMessage = z.infer<typeof DeveloperMessageSchema>;
|
||||
|
||||
export const UserMessageSchema = z.object({
|
||||
type: z.literal(ChatMessageType.User),
|
||||
role: z.literal(ChatMessageRole.User),
|
||||
content: z.string(),
|
||||
});
|
||||
export type UserMessage = z.infer<typeof UserMessageSchema>;
|
||||
|
||||
export const AssistantTextMessageSchema = z.object({
|
||||
type: z.literal(ChatMessageType.AssistantText),
|
||||
role: z.literal(ChatMessageRole.Assistant),
|
||||
content: z.string(),
|
||||
});
|
||||
export type AssistantTextMessage = z.infer<typeof AssistantTextMessageSchema>;
|
||||
|
||||
export const AssistantToolCallMessageSchema = z.object({
|
||||
type: z.literal(ChatMessageType.AssistantToolCall),
|
||||
role: z.literal(ChatMessageRole.Assistant),
|
||||
content: z.string(),
|
||||
toolCalls: z.array(LLMToolCallSchema),
|
||||
});
|
||||
export type AssistantToolCallMessage = z.infer<
|
||||
typeof AssistantToolCallMessageSchema
|
||||
>;
|
||||
|
||||
export const ToolResultMessageSchema = z.object({
|
||||
type: z.literal(ChatMessageType.ToolResult),
|
||||
role: z.literal(ChatMessageRole.Tool),
|
||||
content: z.string(),
|
||||
toolCallId: z.string(),
|
||||
});
|
||||
export type ToolResultMessage = z.infer<typeof ToolResultMessageSchema>;
|
||||
|
||||
export const ChatMessageDefaultRoleSchema = z.nativeEnum(ChatMessageRole);
|
||||
export const ChatMessageSchema = z.union([
|
||||
SystemMessageSchema,
|
||||
DeveloperMessageSchema,
|
||||
UserMessageSchema,
|
||||
AssistantTextMessageSchema,
|
||||
AssistantToolCallMessageSchema,
|
||||
ToolResultMessageSchema,
|
||||
z
|
||||
.object({
|
||||
role: z.union([ChatMessageDefaultRoleSchema, z.string()]), // Users may ingest any string as role via API/SDK
|
||||
content: z.string(),
|
||||
})
|
||||
.transform((msg) => {
|
||||
return {
|
||||
...msg,
|
||||
type: ChatMessageType.PublicAPICreated as const,
|
||||
};
|
||||
}),
|
||||
]);
|
||||
|
||||
export type ChatMessage = z.infer<typeof ChatMessageSchema>;
|
||||
export type ChatMessageWithId = ChatMessage & { id: string };
|
||||
|
||||
export const PromptChatMessageSchema = z.object({
|
||||
role: z.string(),
|
||||
content: z.string(),
|
||||
});
|
||||
export const PromptChatMessageListSchema = z.array(PromptChatMessageSchema);
|
||||
|
||||
export type PromptVariable = { name: string; value: string; isUsed: boolean };
|
||||
|
||||
export enum LLMAdapter {
|
||||
Anthropic = "anthropic",
|
||||
OpenAI = "openai",
|
||||
Atla = "atla",
|
||||
Azure = "azure",
|
||||
Bedrock = "bedrock",
|
||||
VertexAI = "google-vertex-ai",
|
||||
GoogleAIStudio = "google-ai-studio",
|
||||
}
|
||||
|
||||
export enum ChatMessageRole {
|
||||
System = "system",
|
||||
User = "user",
|
||||
Assistant = "assistant",
|
||||
Developer = "developer",
|
||||
}
|
||||
|
||||
export const SYSTEM_ROLES: string[] = [
|
||||
ChatMessageRole.System,
|
||||
ChatMessageRole.Developer,
|
||||
];
|
||||
|
||||
export const ChatMessageDefaultRoleSchema = z.nativeEnum(ChatMessageRole);
|
||||
|
||||
const ChatMessageSchema = z.object({
|
||||
role: z.union([ChatMessageDefaultRoleSchema, z.string()]), // Users may ingest any string as role via API/SDK
|
||||
content: z.string(),
|
||||
});
|
||||
|
||||
export const ChatMessageListSchema = z.array(ChatMessageSchema);
|
||||
export const TextPromptSchema = z.string().min(1, "Enter a prompt");
|
||||
|
||||
export const PromptContentSchema = z.union([
|
||||
ChatMessageListSchema,
|
||||
PromptChatMessageListSchema,
|
||||
TextPromptSchema,
|
||||
]);
|
||||
export type PromptContent = z.infer<typeof PromptContentSchema>;
|
||||
@@ -89,7 +253,16 @@ export const ExperimentMetadataSchema = z
|
||||
export type ExperimentMetadata = z.infer<typeof ExperimentMetadataSchema>;
|
||||
|
||||
// NOTE: Update docs page when changing this! https://langfuse.com/docs/playground#openai-playground--anthropic-playground
|
||||
// WARNING: The first entry in the array is chosen as the default model to add LLM API keys. Make sure it supports top_p, max_tokens and temperature.
|
||||
export const openAIModels = [
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-2025-04-14",
|
||||
"gpt-4.1-mini-2025-04-14",
|
||||
"gpt-4.1-nano-2025-04-14",
|
||||
"o3",
|
||||
"o3-2025-04-16",
|
||||
"o4-mini",
|
||||
"o4-mini-2025-04-16",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-05-13",
|
||||
@@ -101,6 +274,8 @@ export const openAIModels = [
|
||||
"o1-preview-2024-09-12",
|
||||
"o1-mini",
|
||||
"o1-mini-2024-09-12",
|
||||
"gpt-4.5-preview",
|
||||
"gpt-4.5-preview-2025-02-27",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-0613",
|
||||
@@ -118,6 +293,7 @@ export const openAIModels = [
|
||||
export type OpenAIModel = (typeof openAIModels)[number];
|
||||
|
||||
// NOTE: Update docs page when changing this! https://langfuse.com/docs/playground#openai-playground--anthropic-playground
|
||||
// WARNING: The first entry in the array is chosen as the default model to add LLM API keys. Make sure it supports top_p, max_tokens and temperature.
|
||||
export const anthropicModels = [
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
@@ -131,7 +307,9 @@ export const anthropicModels = [
|
||||
"claude-instant-1.2",
|
||||
] as const;
|
||||
|
||||
// WARNING: The first entry in the array is chosen as the default model to add LLM API keys. Make sure it supports top_p, max_tokens and temperature.
|
||||
export const vertexAIModels = [
|
||||
"gemini-2.5-pro-exp-03-25",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
@@ -141,7 +319,9 @@ export const vertexAIModels = [
|
||||
"gemini-1.0-pro",
|
||||
] as const;
|
||||
|
||||
// WARNING: The first entry in the array is chosen as the default model to add LLM API keys. Make sure it supports top_p, max_tokens and temperature.
|
||||
export const googleAIStudioModels = [
|
||||
"gemini-2.5-pro-exp-03-25",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
"gemini-2.0-flash-thinking-exp-01-21",
|
||||
@@ -150,6 +330,8 @@ export const googleAIStudioModels = [
|
||||
"gemini-1.5-flash-8b",
|
||||
] as const;
|
||||
|
||||
export const atlaModels = ["atla-selene", "atla-selene-20250214"] as const;
|
||||
|
||||
export type AnthropicModel = (typeof anthropicModels)[number];
|
||||
export type VertexAIModel = (typeof vertexAIModels)[number];
|
||||
export const supportedModels = {
|
||||
@@ -159,6 +341,7 @@ export const supportedModels = {
|
||||
[LLMAdapter.GoogleAIStudio]: googleAIStudioModels,
|
||||
[LLMAdapter.Azure]: [],
|
||||
[LLMAdapter.Bedrock]: [],
|
||||
[LLMAdapter.Atla]: atlaModels,
|
||||
} as const;
|
||||
|
||||
export type LLMFunctionCall = {
|
||||
|
||||
@@ -383,10 +383,18 @@ export class FilterList {
|
||||
return this.filters.find(predicate);
|
||||
}
|
||||
|
||||
filter(predicate: (filter: Filter) => boolean) {
|
||||
return new FilterList(this.filters.filter(predicate));
|
||||
}
|
||||
|
||||
some(predicate: (filter: Filter) => boolean) {
|
||||
return this.filters.some(predicate);
|
||||
}
|
||||
|
||||
forEach(callback: (filter: Filter) => void) {
|
||||
this.filters.forEach(callback);
|
||||
}
|
||||
|
||||
length() {
|
||||
return this.filters.length;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { singleFilter } from "../../../interfaces/filters";
|
||||
import { FilterCondition } from "../../../types";
|
||||
import { isValidTableName } from "../../clickhouse/schemaUtils";
|
||||
import { logger } from "../../logger";
|
||||
import { UiColumnMapping } from "../../../tableDefinitions";
|
||||
import { UiColumnMappings } from "../../../tableDefinitions";
|
||||
import {
|
||||
StringFilter,
|
||||
DateTimeFilter,
|
||||
@@ -29,7 +29,7 @@ export class QueryBuilderError extends Error {
|
||||
// User input for values (e.g. project_id = <value>) are sent to Clickhouse as parameters to prevent SQL injection
|
||||
export const createFilterFromFilterState = (
|
||||
filter: FilterCondition[],
|
||||
columnMapping: UiColumnMapping[],
|
||||
columnMapping: UiColumnMappings,
|
||||
) => {
|
||||
return filter.map((frontEndFilter) => {
|
||||
// checks if the column exists in the clickhouse schema
|
||||
@@ -120,7 +120,7 @@ export const createFilterFromFilterState = (
|
||||
|
||||
const matchAndVerifyTracesUiColumn = (
|
||||
filter: z.infer<typeof singleFilter>,
|
||||
uiTableDefinitions: UiColumnMapping[],
|
||||
uiTableDefinitions: UiColumnMappings,
|
||||
) => {
|
||||
// tries to match the column name to the clickhouse table name
|
||||
logger.debug(`Filter to match: ${JSON.stringify(filter)}`);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import z from "zod";
|
||||
import { OrderByState } from "../../../interfaces/orderBy";
|
||||
import { UiColumnMapping } from "../../../tableDefinitions";
|
||||
import { UiColumnMappings } from "../../../tableDefinitions";
|
||||
import { logger } from "../../logger";
|
||||
|
||||
type OrderByStateNotNull = Exclude<OrderByState, null>;
|
||||
|
||||
export function orderByToClickhouseSql(
|
||||
orderBy: OrderByState | OrderByState[] = [],
|
||||
tableColumns: UiColumnMapping[],
|
||||
tableColumns: UiColumnMappings,
|
||||
): string {
|
||||
if (
|
||||
!orderBy ||
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { ObservationView } from "../repositories";
|
||||
import { Observation } from "../../domain";
|
||||
|
||||
type AdditionalObservationFields = {
|
||||
traceName: string | null;
|
||||
traceTags: Array<string>;
|
||||
usageDetails: Record<string, number>;
|
||||
costDetails: Record<string, number>;
|
||||
traceTimestamp: Date | null;
|
||||
};
|
||||
|
||||
export type FullObservation = AdditionalObservationFields & ObservationView;
|
||||
export type FullObservation = AdditionalObservationFields & Observation;
|
||||
|
||||
export type FullObservations = Array<FullObservation>;
|
||||
|
||||
export type FullObservationsWithScores = Array<
|
||||
FullObservation & { scores?: Record<string, string[] | number[]> | null }
|
||||
>;
|
||||
|
||||
export type IOAndMetadataOmittedObservations = Array<
|
||||
Omit<ObservationView, "input" | "output" | "metadata"> &
|
||||
AdditionalObservationFields
|
||||
>;
|
||||
|
||||
@@ -2,7 +2,6 @@ export { createSessionsAllQuery } from "./createSessionsAllQuery";
|
||||
export {
|
||||
type FullObservations,
|
||||
type FullObservationsWithScores,
|
||||
type IOAndMetadataOmittedObservations,
|
||||
} from "./createGenerationsQuery";
|
||||
export {
|
||||
FilterList,
|
||||
@@ -18,3 +17,4 @@ export {
|
||||
type ClickhouseOperator,
|
||||
} from "./clickhouse-sql/clickhouse-filter";
|
||||
export { orderByToClickhouseSql } from "./clickhouse-sql/orderby-factory";
|
||||
export { createFilterFromFilterState } from "./clickhouse-sql/factory";
|
||||
|
||||
@@ -17,7 +17,6 @@ export const IngestionEvent = z.object({
|
||||
validKey: z.literal(true),
|
||||
scope: z.object({
|
||||
projectId: z.string(),
|
||||
accessLevel: z.enum(["all", "scores"]),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -34,6 +33,10 @@ export const TracesQueueEventSchema = z.object({
|
||||
projectId: z.string(),
|
||||
traceIds: z.array(z.string()),
|
||||
});
|
||||
export const ScoresQueueEventSchema = z.object({
|
||||
projectId: z.string(),
|
||||
scoreIds: z.array(z.string()),
|
||||
});
|
||||
export const ProjectQueueEventSchema = z.object({
|
||||
projectId: z.string(),
|
||||
orgId: z.string(),
|
||||
@@ -52,6 +55,9 @@ export const EvalExecutionEvent = z.object({
|
||||
export const PostHogIntegrationProcessingEventSchema = z.object({
|
||||
projectId: z.string(),
|
||||
});
|
||||
export const BlobStorageIntegrationProcessingEventSchema = z.object({
|
||||
projectId: z.string(),
|
||||
});
|
||||
export const ExperimentCreateEventSchema = z.object({
|
||||
projectId: z.string(),
|
||||
datasetId: z.string(),
|
||||
@@ -65,6 +71,15 @@ export const DataRetentionProcessingEventSchema = z.object({
|
||||
export const BatchActionProcessingEventSchema = z.discriminatedUnion(
|
||||
"actionId",
|
||||
[
|
||||
z.object({
|
||||
actionId: z.literal("score-delete"),
|
||||
projectId: z.string(),
|
||||
query: BatchActionQuerySchema,
|
||||
tableName: z.nativeEnum(BatchExportTableName),
|
||||
cutoffCreatedAt: z.date(),
|
||||
targetId: z.string().optional(),
|
||||
type: z.nativeEnum(BatchActionType),
|
||||
}),
|
||||
z.object({
|
||||
actionId: z.literal("trace-delete"),
|
||||
projectId: z.string(),
|
||||
@@ -114,6 +129,7 @@ export type CreateEvalQueueEventType = z.infer<
|
||||
export type BatchExportJobType = z.infer<typeof BatchExportJobSchema>;
|
||||
export type TraceQueueEventType = z.infer<typeof TraceQueueEventSchema>;
|
||||
export type TracesQueueEventType = z.infer<typeof TracesQueueEventSchema>;
|
||||
export type ScoresQueueEventType = z.infer<typeof ScoresQueueEventSchema>;
|
||||
export type ProjectQueueEventType = z.infer<typeof ProjectQueueEventSchema>;
|
||||
export type DatasetRunItemUpsertEventType = z.infer<
|
||||
typeof DatasetRunItemUpsertEventSchema
|
||||
@@ -132,6 +148,9 @@ export type DataRetentionProcessingEventType = z.infer<
|
||||
export type BatchActionProcessingEventType = z.infer<
|
||||
typeof BatchActionProcessingEventSchema
|
||||
>;
|
||||
export type BlobStorageIntegrationProcessingEventType = z.infer<
|
||||
typeof BlobStorageIntegrationProcessingEventSchema
|
||||
>;
|
||||
|
||||
export enum QueueName {
|
||||
TraceUpsert = "trace-upsert", // Ingestion pipeline adds events on each Trace upsert
|
||||
@@ -146,12 +165,15 @@ export enum QueueName {
|
||||
ExperimentCreate = "experiment-create-queue",
|
||||
PostHogIntegrationQueue = "posthog-integration-queue",
|
||||
PostHogIntegrationProcessingQueue = "posthog-integration-processing-queue",
|
||||
BlobStorageIntegrationQueue = "blobstorage-integration-queue",
|
||||
BlobStorageIntegrationProcessingQueue = "blobstorage-integration-processing-queue",
|
||||
CoreDataS3ExportQueue = "core-data-s3-export-queue",
|
||||
MeteringDataPostgresExportQueue = "metering-data-postgres-export-queue",
|
||||
DataRetentionQueue = "data-retention-queue",
|
||||
DataRetentionProcessingQueue = "data-retention-processing-queue",
|
||||
BatchActionQueue = "batch-action-queue",
|
||||
CreateEvalQueue = "create-eval-queue",
|
||||
ScoreDelete = "score-delete",
|
||||
}
|
||||
|
||||
export enum QueueJobs {
|
||||
@@ -167,12 +189,15 @@ export enum QueueJobs {
|
||||
ExperimentCreateJob = "experiment-create-job",
|
||||
PostHogIntegrationJob = "posthog-integration-job",
|
||||
PostHogIntegrationProcessingJob = "posthog-integration-processing-job",
|
||||
BlobStorageIntegrationJob = "blobstorage-integration-job",
|
||||
BlobStorageIntegrationProcessingJob = "blobstorage-integration-processing-job",
|
||||
CoreDataS3ExportJob = "core-data-s3-export-job",
|
||||
MeteringDataPostgresExportJob = "metering-data-postgres-export-job",
|
||||
DataRetentionJob = "data-retention-job",
|
||||
DataRetentionProcessingJob = "data-retention-processing-job",
|
||||
BatchActionProcessingJob = "batch-action-processing-job",
|
||||
CreateEvalJob = "create-eval-job",
|
||||
ScoreDelete = "score-delete",
|
||||
}
|
||||
|
||||
export type TQueueJobTypes = {
|
||||
@@ -188,6 +213,12 @@ export type TQueueJobTypes = {
|
||||
payload: TracesQueueEventType | TraceQueueEventType;
|
||||
name: QueueJobs.TraceDelete;
|
||||
};
|
||||
[QueueName.ScoreDelete]: {
|
||||
timestamp: Date;
|
||||
id: string;
|
||||
payload: ScoresQueueEventType;
|
||||
name: QueueJobs.ScoreDelete;
|
||||
};
|
||||
[QueueName.ProjectDelete]: {
|
||||
timestamp: Date;
|
||||
id: string;
|
||||
@@ -254,4 +285,10 @@ export type TQueueJobTypes = {
|
||||
payload: CreateEvalQueueEventType;
|
||||
name: QueueJobs.CreateEvalJob;
|
||||
};
|
||||
[QueueName.BlobStorageIntegrationProcessingQueue]: {
|
||||
timestamp: Date;
|
||||
id: string;
|
||||
payload: BlobStorageIntegrationProcessingEventType;
|
||||
name: QueueJobs.BlobStorageIntegrationProcessingJob;
|
||||
};
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user