Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cef46cd78 | ||
|
|
243d72772c | ||
|
|
91214cc6a6 | ||
|
|
da85e80689 | ||
|
|
e22827d380 | ||
|
|
7161643574 | ||
|
|
77f1c6cd0c | ||
|
|
0a4b56eb28 | ||
|
|
b5f94d78f3 | ||
|
|
a9fd1e1832 | ||
|
|
762a7e3489 | ||
|
|
0f3717db0d | ||
|
|
8590ab84ba | ||
|
|
9d17903392 | ||
|
|
7d94831c01 | ||
|
|
b97900f5b8 | ||
|
|
887947a967 | ||
|
|
85661c0e58 | ||
|
|
fc2ba97792 | ||
|
|
4b6cd3fd2b | ||
|
|
d758821817 | ||
|
|
4685d0b1b6 | ||
|
|
e88946f0b5 | ||
|
|
f14c611aac | ||
|
|
84ccdc24ed | ||
|
|
04a0366af5 | ||
|
|
1a956c909c | ||
|
|
8e150701cb | ||
|
|
b9d8b5026e | ||
|
|
a6dfc58c72 | ||
|
|
6266cd8f0b | ||
|
|
e6e6328594 | ||
|
|
8b8826437a | ||
|
|
b77c51512e | ||
|
|
10d2299fae | ||
|
|
83e1eb50bd | ||
|
|
c7546bcafd | ||
|
|
b26a537181 | ||
|
|
e5cbca7f95 | ||
|
|
73c7a554c2 | ||
|
|
c460029704 | ||
|
|
2808157200 | ||
|
|
79913e3816 | ||
|
|
c9b79f4277 | ||
|
|
5897db3880 | ||
|
|
4a4d08d9f1 | ||
|
|
f383a3f50f | ||
|
|
79954cf188 | ||
|
|
f87adc209a | ||
|
|
3a71bfd763 | ||
|
|
c8ff61057e | ||
|
|
10d5975e26 | ||
|
|
7b34c6fc32 | ||
|
|
1ac6e51271 | ||
|
|
8435080ec7 | ||
|
|
3b474e596d | ||
|
|
16b40917c6 | ||
|
|
d0e5a6bfe3 | ||
|
|
0048dbfae2 | ||
|
|
bc612a8384 | ||
|
|
541765f8a0 | ||
|
|
7b4663e373 | ||
|
|
5eb792e2f5 | ||
|
|
5499da8cc3 | ||
|
|
35b212954c | ||
|
|
11c791ae38 | ||
|
|
4ab1cbd03c | ||
|
|
d33fb015d5 | ||
|
|
5b1704c95e | ||
|
|
03659f3e82 | ||
|
|
cf3b7f4603 | ||
|
|
07c6c95eff | ||
|
|
74cbe04fe9 | ||
|
|
05aa62aa9e | ||
|
|
4acb01086b | ||
|
|
d12941576b | ||
|
|
d99f12fe64 | ||
|
|
187e64c8d2 | ||
|
|
de152e4728 | ||
|
|
d3669eeef2 | ||
|
|
38a453eebb | ||
|
|
53dc3c3473 | ||
|
|
b573d9035f | ||
|
|
bc0485c91a | ||
|
|
63fa223e54 | ||
|
|
06b3839fd2 | ||
|
|
254c399ec6 | ||
|
|
6567e93a96 | ||
|
|
7f6de12256 | ||
|
|
5090e521a1 | ||
|
|
81847e7bc5 | ||
|
|
b4fff64169 | ||
|
|
3cba71d5be | ||
|
|
ce6a96f3d1 | ||
|
|
f7a00ff0eb | ||
|
|
56306bb456 | ||
|
|
eab3268386 | ||
|
|
f68dfac8b4 | ||
|
|
bb76ae4aea | ||
|
|
7babcbd170 | ||
|
|
ef12dd1b5f | ||
|
|
6200a9844f | ||
|
|
1e5717d13d | ||
|
|
c97ed69874 | ||
|
|
c258776a68 | ||
|
|
892ff766e8 | ||
|
|
80470cb812 | ||
|
|
5949a659a9 | ||
|
|
151d305b02 | ||
|
|
74819a1d1f | ||
|
|
45edcde550 | ||
|
|
2b04b9d9b8 | ||
|
|
3150c655fd | ||
|
|
26375885a9 | ||
|
|
b584684593 | ||
|
|
6f00b67055 | ||
|
|
cdbae876e8 | ||
|
|
d1c61b4eba | ||
|
|
e443730e66 | ||
|
|
4c55165c2f | ||
|
|
34a4a22cba | ||
|
|
4cb688929e | ||
|
|
0b86f3207b | ||
|
|
4a83749425 | ||
|
|
8b686c9eca | ||
|
|
1c8b81843c | ||
|
|
d26a54fc02 | ||
|
|
39ce15aa4d | ||
|
|
7e933e89cb | ||
|
|
3373943f24 | ||
|
|
194f2f61f5 | ||
|
|
4ed1ad7bc2 | ||
|
|
3129baa2c6 | ||
|
|
fa7cdca96b | ||
|
|
736855b5fa | ||
|
|
3fa2cc8957 | ||
|
|
33e6be3215 | ||
|
|
9c2884a56f | ||
|
|
5734a9619a | ||
|
|
a3d58f6727 | ||
|
|
408fb64c69 | ||
|
|
5200390427 | ||
|
|
6f2be5225b | ||
|
|
f01a110bce | ||
|
|
b6e49b440e | ||
|
|
ff6e0648f3 | ||
|
|
c4fa31be9f | ||
|
|
e68394abc6 | ||
|
|
c24032391d | ||
|
|
6c7faf61f8 | ||
|
|
43bedf2afe | ||
|
|
674bdbd976 | ||
|
|
ed1f6d8563 | ||
|
|
b49d335eb0 | ||
|
|
e9bc76fe89 | ||
|
|
7cdbc7f464 | ||
|
|
237e539c4c | ||
|
|
b1357a0bae | ||
|
|
fa51789ac2 | ||
|
|
894ffdda69 | ||
|
|
0c2e6a5b5d | ||
|
|
0d7e5bfcca | ||
|
|
7cc5a9e229 | ||
|
|
629b30e42d | ||
|
|
3e98df566c | ||
|
|
0a88294202 | ||
|
|
6e9f5ace41 | ||
|
|
1b662c95e5 | ||
|
|
87e94289ff | ||
|
|
ed08331927 | ||
|
|
02e5c0119a | ||
|
|
adc39d58e4 | ||
|
|
19987e9816 | ||
|
|
746299431f | ||
|
|
74224a04cd | ||
|
|
874d574a9e | ||
|
|
13655515a2 | ||
|
|
204ce02bd1 | ||
|
|
b87f84bf67 | ||
|
|
e139b40167 | ||
|
|
f3596fac10 | ||
|
|
f1d15ea268 | ||
|
|
5183cf4859 | ||
|
|
604ff3203e | ||
|
|
ca8ba2f233 | ||
|
|
f9c5534a20 | ||
|
|
f652c75304 | ||
|
|
879a72b821 | ||
|
|
d3cb8469c0 | ||
|
|
b5b93cf710 | ||
|
|
27caeb1536 | ||
|
|
3b40c67aae | ||
|
|
79a6bc115f | ||
|
|
8aa1e0e1b8 | ||
|
|
217926b049 | ||
|
|
8ed92de662 | ||
|
|
2a9421674b | ||
|
|
0b2bf8535d | ||
|
|
b89e2423c3 | ||
|
|
43340dd6ff | ||
|
|
e529d887ba | ||
|
|
d7d79df5cf | ||
|
|
59de947865 | ||
|
|
c5ab61b356 | ||
|
|
13db6781e2 | ||
|
|
5442429f47 | ||
|
|
c01600b2b8 | ||
|
|
19101723b3 | ||
|
|
989522b22a | ||
|
|
8b4a2893d5 | ||
|
|
6719dd4158 | ||
|
|
94d87f2a42 | ||
|
|
e06272c4df | ||
|
|
f6512d7a8e | ||
|
|
5e67d897b7 | ||
|
|
9adcfde4cc | ||
|
|
bc8f8656a1 | ||
|
|
85a327ef26 | ||
|
|
46b20f4107 | ||
|
|
fb8f26a00e | ||
|
|
3d7ba475e2 | ||
|
|
03028a92b4 | ||
|
|
8a96290f03 | ||
|
|
bca09bdefe | ||
|
|
2d314250e9 | ||
|
|
4a87f402ec | ||
|
|
800b2136b9 | ||
|
|
278df5ab3c | ||
|
|
c7f9e18768 | ||
|
|
2121b40cca | ||
|
|
5ba440f2de | ||
|
|
657a3275de | ||
|
|
56bb9c8dcf | ||
|
|
66e15bcf0d | ||
|
|
5f94ca8e82 | ||
|
|
e8561fb105 | ||
|
|
18e84c6498 | ||
|
|
05152ac8d6 | ||
|
|
6cb9e24ba2 | ||
|
|
484fa424fb | ||
|
|
555aebe8c7 | ||
|
|
af29424b48 | ||
|
|
42f1c63bbf | ||
|
|
663ded0743 | ||
|
|
d7ced99d16 | ||
|
|
c979cae620 | ||
|
|
c483293b10 | ||
|
|
932913cd76 | ||
|
|
5875537132 | ||
|
|
1a53981a7a | ||
|
|
0e6b676b19 | ||
|
|
955e25555f | ||
|
|
2a11f1e8fb | ||
|
|
143a461f0c | ||
|
|
c8544690f7 | ||
|
|
05a533dd7e | ||
|
|
f3d15bb36e | ||
|
|
24c321f632 | ||
|
|
0e9cf548b2 | ||
|
|
7158a41fe9 | ||
|
|
bc384a525a | ||
|
|
3f70828df6 | ||
|
|
0988635427 | ||
|
|
41ed6fed5e | ||
|
|
eb40659e44 | ||
|
|
a22bcb1188 | ||
|
|
392fbe6c8c | ||
|
|
48d5a8f5a1 | ||
|
|
44428870f7 | ||
|
|
724a4f5d9a | ||
|
|
2358264b66 | ||
|
|
65e3b4c5ca | ||
|
|
62c413faf5 | ||
|
|
8ea14ac537 | ||
|
|
c32b84663c | ||
|
|
23ab79ba42 | ||
|
|
6ffec3337f | ||
|
|
34a2a9cddf | ||
|
|
0854429850 | ||
|
|
05fd690486 | ||
|
|
bc3f39272b | ||
|
|
96ace71f9a | ||
|
|
70415dcab5 | ||
|
|
6a18f249aa | ||
|
|
cc60d506a7 | ||
|
|
a31a7f238e | ||
|
|
41ebc77f86 | ||
|
|
cdc41cf573 | ||
|
|
c86e32f756 | ||
|
|
85576c162a | ||
|
|
93ad1e6880 | ||
|
|
97b17fc8bb | ||
|
|
04e2d9249f | ||
|
|
cb94a54f0c | ||
|
|
7c90bbed0c | ||
|
|
f133526ba6 | ||
|
|
dc014de54c | ||
|
|
c6beec1ad5 | ||
|
|
a810f7750f | ||
|
|
2b34c82e98 | ||
|
|
fe283284ef | ||
|
|
307f3d18ce | ||
|
|
46fff7148e | ||
|
|
9cce111be6 | ||
|
|
dbd7f810c1 | ||
|
|
abc3ea2b5a | ||
|
|
3d8a4f9d75 | ||
|
|
7b4bef7c2e | ||
|
|
b10353115b | ||
|
|
f3235ceccb | ||
|
|
92455b4e5a | ||
|
|
217b92d548 | ||
|
|
c6ddd3822f | ||
|
|
67d6c32ec5 | ||
|
|
d89379e693 | ||
|
|
4b8d3a764e | ||
|
|
5757fa6e38 | ||
|
|
34c3e5af08 | ||
|
|
c68ec2fca0 | ||
|
|
03dda153e8 | ||
|
|
63641521d8 | ||
|
|
29d11642c4 | ||
|
|
fb98a404d2 | ||
|
|
032fce56c4 | ||
|
|
518b8df2da | ||
|
|
70761f7ab0 | ||
|
|
a85d4ccf07 | ||
|
|
1b42eae7dd | ||
|
|
898cca56f9 | ||
|
|
83e052e769 | ||
|
|
b5f19771fa | ||
|
|
644390183b | ||
|
|
4d8e9003fe | ||
|
|
651c46a270 | ||
|
|
6b127bced0 | ||
|
|
e3a837b561 | ||
|
|
11c04f02c8 | ||
|
|
536aa138bc | ||
|
|
61b596c46a | ||
|
|
ddf43c0836 | ||
|
|
d5ab03892a | ||
|
|
918b6643d2 | ||
|
|
937b4c0f0f | ||
|
|
9b7f046e71 | ||
|
|
fe63704508 | ||
|
|
c39279f677 | ||
|
|
4206f5025d | ||
|
|
0260a4e861 | ||
|
|
244adf0dc3 | ||
|
|
dc6b98d009 | ||
|
|
b20fc5a20b | ||
|
|
f59ad5fd60 | ||
|
|
06ef41e372 | ||
|
|
e215a2db99 | ||
|
|
5e247e88c9 | ||
|
|
8e1e27c868 | ||
|
|
4648a5be8b |
+1
-1
@@ -1,4 +1,4 @@
|
||||
[codespell]
|
||||
skip = .git,*.pdf,*.svg,package-lock.json,*.prisma,pnpm-lock.yaml
|
||||
ignore-words-list = afterall,vertx
|
||||
ignore-words-list = afterall,vertx,notIn
|
||||
|
||||
|
||||
+22
-6
@@ -34,11 +34,25 @@ EMAIL_FROM_ADDRESS="" # Defines the email address to use as the from address.
|
||||
SMTP_CONNECTION_URL="" # Defines the connection url for smtp server.
|
||||
|
||||
# S3 storage
|
||||
S3_ENDPOINT=
|
||||
S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
S3_BUCKET_NAME=
|
||||
S3_REGION=
|
||||
S3_ENDPOINT=http://localhost:9090
|
||||
S3_ACCESS_KEY_ID=minio
|
||||
S3_SECRET_ACCESS_KEY=miniosecret
|
||||
S3_BUCKET_NAME=langfuse
|
||||
S3_REGION=us-east-1
|
||||
## Necessary for minio compatibility
|
||||
S3_FORCE_PATH_STYLE=true
|
||||
|
||||
# S3 Event Bucket Upload
|
||||
## Set to true to test uploading all events to S3
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ENABLED=false
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio
|
||||
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=miniosecret
|
||||
LANGFUSE_S3_EVENT_UPLOAD_REGION=us-east-1
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://localhost:9090
|
||||
## Necessary for minio compatibility
|
||||
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
|
||||
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
|
||||
|
||||
# Set during docker build of application
|
||||
# Used to disable environment verification at build time
|
||||
@@ -48,6 +62,8 @@ REDIS_HOST="127.0.0.1"
|
||||
REDIS_PORT=6379
|
||||
REDIS_AUTH="myredissecret"
|
||||
|
||||
LANGFUSE_WORKER_PASSWORD=mybasicauthsecret
|
||||
# openssl rand -hex 32 used only here
|
||||
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# speeds up local development by not executing init scripts on server startup
|
||||
NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT="false"
|
||||
@@ -18,7 +18,5 @@ REDIS_HOST="127.0.0.1"
|
||||
REDIS_PORT=6379
|
||||
REDIS_AUTH="myredissecret"
|
||||
|
||||
LANGFUSE_WORKER_PASSWORD=myworkerpassword
|
||||
|
||||
# openssl rand -hex 32 used only here
|
||||
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
+47
-7
@@ -28,17 +28,28 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
|
||||
# Use CSP headers to enforce HTTPS, optional
|
||||
# LANGFUSE_CSP_ENFORCE_HTTPS="true"
|
||||
|
||||
# Configure base path for self-hosting, optional
|
||||
# Note: You need to build the docker image with the base path set and cannot use the pre-built docker image if you set this.
|
||||
# NEXT_PUBLIC_BASE_PATH="/app"
|
||||
|
||||
# Docker only, optional
|
||||
# PORT=3000
|
||||
# HOSTNAME=localhost
|
||||
|
||||
# Opentelemetry, optional
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
|
||||
OTEL_SERVICE_NAME="langfuse"
|
||||
|
||||
# Default role for users who sign up, optional, can be org or org+project
|
||||
# LANGFUSE_DEFAULT_ORG_ID=
|
||||
# LANGFUSE_DEFAULT_ORG_ROLE=
|
||||
# LANGFUSE_DEFAULT_PROJECT_ID=
|
||||
# LANGFUSE_DEFAULT_PROJECT_ROLE=
|
||||
|
||||
# Logging, optional
|
||||
# LANGFUSE_LOG_LEVEL=info
|
||||
# LANGFUSE_LOG_FORMAT=text
|
||||
|
||||
# Enable experimental features, optional
|
||||
# LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=true
|
||||
|
||||
@@ -56,6 +67,10 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
|
||||
# AUTH_GITHUB_CLIENT_ID=
|
||||
# AUTH_GITHUB_CLIENT_SECRET=
|
||||
# AUTH_GITHUB_ALLOW_ACCOUNT_LINKING=false
|
||||
# AUTH_GITLAB_CLIENT_ID=
|
||||
# AUTH_GITLAB_CLIENT_SECRET=
|
||||
# AUTH_GITLAB_ALLOW_ACCOUNT_LINKING=false
|
||||
# AUTH_GITLAB_ISSUER=
|
||||
# AUTH_AZURE_AD_CLIENT_ID=
|
||||
# AUTH_AZURE_AD_CLIENT_SECRET=
|
||||
# AUTH_AZURE_AD_TENANT_ID=
|
||||
@@ -93,10 +108,33 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
|
||||
# S3_REGION=
|
||||
# BATCH_EXPORT_DOWNLOAD_LINK_EXPIRATION_HOURS=
|
||||
|
||||
# S3 storage for events, optional, used to persist all incoming events
|
||||
# LANGFUSE_S3_EVENT_UPLOAD_ENABLED="true"
|
||||
# LANGFUSE_S3_EVENT_UPLOAD_BUCKET=
|
||||
# Optional prefix to be used within the bucket. Must end with `/` if set
|
||||
# LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
|
||||
# The following four options are optional and fallback to the normal SDK credential provider chain if omitted
|
||||
# See https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
|
||||
# LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=
|
||||
# LANGFUSE_S3_EVENT_UPLOAD_REGION=
|
||||
# LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=
|
||||
# LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=
|
||||
|
||||
# Exports are streamed to S3 in pages to avoid memory issues
|
||||
# The page size can be adjusted if needed to optimize performance
|
||||
# DB_EXPORT_PAGE_SIZE=1000
|
||||
|
||||
# Automated provisioning of default resources
|
||||
# LANGFUSE_INIT_ORG_ID=org-id
|
||||
# LANGFUSE_INIT_ORG_NAME=org-name
|
||||
# LANGFUSE_INIT_PROJECT_ID=project-id
|
||||
# LANGFUSE_INIT_PROJECT_NAME=project-name
|
||||
# LANGFUSE_INIT_PROJECT_PUBLIC_KEY=pk-1234567890
|
||||
# LANGFUSE_INIT_PROJECT_SECRET_KEY=sk-1234567890
|
||||
# LANGFUSE_INIT_USER_EMAIL=user@example.com
|
||||
# LANGFUSE_INIT_USER_NAME=User Name
|
||||
# LANGFUSE_INIT_USER_PASSWORD=password
|
||||
|
||||
|
||||
|
||||
### START Enterprise Edition Configuration
|
||||
@@ -109,6 +147,12 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
|
||||
# LANGFUSE_UI_DOCUMENTATION_HREF=https://docs.example.com
|
||||
# LANGFUSE_UI_SUPPORT_HREF=https://support.example.com
|
||||
# LANGFUSE_UI_FEEDBACK_HREF=https://feedback.example.com
|
||||
# LANGFUSE_UI_LOGO_LIGHT_MODE_HREF=https://static.langfuse.com/langfuse-dev/example-logo-light-mode.png
|
||||
# LANGFUSE_UI_LOGO_DARK_MODE_HREF=https://static.langfuse.com/langfuse-dev/example-logo-dark-mode.png
|
||||
# LANGFUSE_UI_DEFAULT_MODEL_ADAPTER=Anthropic # OpenAI, Anthropic, Azure
|
||||
# LANGFUSE_UI_DEFAULT_BASE_URL_OPENAI=https://api.openai.com/v1
|
||||
# LANGFUSE_UI_DEFAULT_BASE_URL_ANTHROPIC=https://api.anthropic.com
|
||||
# LANGFUSE_UI_DEFAULT_BASE_URL_AZURE_OPENAI=https://{instanceName}.openai.azure.com/openai/deployments
|
||||
|
||||
### END Enterprise Edition Configuration
|
||||
|
||||
@@ -145,7 +189,6 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
|
||||
# NEXT_SENTRY_PROJECT=
|
||||
# SENTRY_AUTH_TOKEN=
|
||||
# SENTRY_CSP_REPORT_URI=
|
||||
# LANGFUSE_WORKER_BETTERSTACK_TOKEN=
|
||||
|
||||
|
||||
# Cloudflare Turnstile
|
||||
@@ -194,15 +237,12 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
|
||||
# CLICKHOUSE_PASSWORD=
|
||||
|
||||
# Ingestion
|
||||
# LANGFUSE_INGESTION_BUFFER_TTL_SECONDS=
|
||||
# LANGFUSE_INGESTION_FLUSH_DELAY_MS=
|
||||
# LANGFUSE_INGESTION_FLUSH_ATTEMPTS=
|
||||
# LANGFUSE_INGESTION_FLUSH_PROCESSING_CONCURRENCY=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_BATCH_SIZE=
|
||||
# LANGFUSE_INGESTION_QUEUE_DELAY_SECONDS=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_BATCH_SIZE=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_MAX_ATTEMPTS=
|
||||
# LANGFUSE_LOG_LEVEL=
|
||||
# LANGFUSE_LEGACY_INGESTION_WORKER_CONCURRENCY=
|
||||
# LANGFUSE_ASYNC_INGESTION_PROCESSING="true"
|
||||
# QUEUE_CONSUMER_LEGACY_INGESTION_QUEUE_IS_ENABLED="true"
|
||||
|
||||
## END Langfuse V3 Ingestion
|
||||
@@ -32,6 +32,10 @@ updates:
|
||||
patterns:
|
||||
- "lodash"
|
||||
- "@types/lodash"
|
||||
express:
|
||||
patterns:
|
||||
- "express"
|
||||
- "@types/express"
|
||||
observability:
|
||||
patterns:
|
||||
- "dd-trace"
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
name: WorkflowCall - Deploy to ECS Service
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
environment:
|
||||
type: string
|
||||
description: Deployment environment
|
||||
required: true
|
||||
service:
|
||||
type: string
|
||||
description: Name of the service to be deployed, e.g. web-ingestion, web, or worker.
|
||||
required: true
|
||||
jobs:
|
||||
ecs-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.environment }}
|
||||
steps:
|
||||
- name: Get app name
|
||||
uses: winterjung/split@v2
|
||||
id: split
|
||||
with:
|
||||
msg: ${{ inputs.service }}
|
||||
separator: "-"
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Authenticate with AWS
|
||||
# GitHub/AWS recommend to use OIDC here: https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#oidc
|
||||
# Probably more painful to configure, but would remove all long-lived credentials.
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
- name: Login to AWS ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
- name: Build, tag, and push Docker image
|
||||
env:
|
||||
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
||||
REPOSITORY: ${{ steps.split.outputs._0 }}
|
||||
IMAGE_TAG: ${{ github.sha }}
|
||||
run: |
|
||||
docker build \
|
||||
-t $REGISTRY/$REPOSITORY:$IMAGE_TAG \
|
||||
-f ./${{ steps.split.outputs._0 }}/Dockerfile \
|
||||
--build-arg NEXT_PUBLIC_LANGFUSE_CLOUD_REGION=${{ vars.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION }} \
|
||||
--build-arg NEXT_LANGFUSE_TRACING_SAMPLE_RATE=${{ vars.NEXT_LANGFUSE_TRACING_SAMPLE_RATE }} \
|
||||
--build-arg NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }} \
|
||||
--build-arg NEXT_PUBLIC_DEMO_ORG_ID=${{ vars.NEXT_PUBLIC_DEMO_ORG_ID }} \
|
||||
--build-arg NEXT_PUBLIC_DEMO_PROJECT_ID=${{ vars.NEXT_PUBLIC_DEMO_PROJECT_ID }} \
|
||||
--build-arg NEXT_PUBLIC_SENTRY_DSN=${{ vars.NEXT_PUBLIC_SENTRY_DSN }} \
|
||||
--build-arg NEXT_PUBLIC_BUILD_ID=${{ github.sha }} \
|
||||
--build-arg NEXT_PUBLIC_POSTHOG_KEY=${{ vars.NEXT_PUBLIC_POSTHOG_KEY }} \
|
||||
--build-arg NEXT_PUBLIC_POSTHOG_HOST=${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} \
|
||||
--build-arg NEXT_PUBLIC_CRISP_WEBSITE_ID=${{ vars.NEXT_PUBLIC_CRISP_WEBSITE_ID }} \
|
||||
--build-arg SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \
|
||||
--build-arg SENTRY_ORG=${{ vars.SENTRY_ORG }} \
|
||||
--build-arg SENTRY_PROJECT=${{ vars.SENTRY_PROJECT }} \
|
||||
.
|
||||
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
|
||||
- name: Render AWS ECS Task Definition
|
||||
id: render-task-definition
|
||||
uses: aws-actions/amazon-ecs-render-task-definition@v1
|
||||
with:
|
||||
container-name: ${{ inputs.service }}
|
||||
image: ${{ steps.login-ecr.outputs.registry }}/${{ steps.split.outputs._0 }}:${{ github.sha }}
|
||||
task-definition-family: ${{ inputs.environment }}-${{ inputs.service }}
|
||||
- name: Update AWS ECS Service
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||
with:
|
||||
task-definition: ${{ steps.render-task-definition.outputs.task-definition }}
|
||||
service: ${{ inputs.environment }}-${{ inputs.service }}
|
||||
cluster: ${{ inputs.environment }}-cluster
|
||||
wait-for-service-stability: true
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Start containers
|
||||
run: docker compose -f "docker-compose.yml" up -d --build
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
|
||||
- name: Setup Node 18
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "production", "v*"]
|
||||
pull_request:
|
||||
branches: ["main", "production", "v*"]
|
||||
schedule:
|
||||
- cron: "31 13 * * 0"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -0,0 +1,105 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
service:
|
||||
description: 'Service to be deployed'
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- web
|
||||
- web-ingestion
|
||||
- worker
|
||||
required: true
|
||||
environment:
|
||||
description: 'Environment to deploy to'
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- prod-eu
|
||||
- prod-us
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
# Support concurrent `push` and `workflow_dispatch`` actions
|
||||
group: deploy-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
name: Deploy to ECS
|
||||
jobs:
|
||||
affected-services:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
services: ${{ steps.affected-services.outputs.result }}
|
||||
steps:
|
||||
- name: Get affected services
|
||||
id: affected-services
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
if (context.eventName === "workflow_dispatch") {
|
||||
if (context.payload.inputs.service === "all") {
|
||||
return `["web", "web-ingestion", "worker"]`
|
||||
}
|
||||
return `["${context.payload.inputs.service}"]`
|
||||
}
|
||||
if (context.eventName === "push") {
|
||||
return `["web", "web-ingestion", "worker"]`
|
||||
}
|
||||
return "[]"
|
||||
result-encoding: string
|
||||
- name: Print services to build
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
services: ${{ steps.affected-services.outputs.result }}
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
console.log('Services', `${process.env.services}` ?? 'n/a');
|
||||
|
||||
affected-environments:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
environments: ${{ steps.affected-environments.outputs.result }}
|
||||
steps:
|
||||
- name: Get affected environments
|
||||
id: affected-environments
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
if (context.eventName === "workflow_dispatch") {
|
||||
return `["${context.payload.inputs.environment}"]`
|
||||
}
|
||||
if (context.eventName === "push") {
|
||||
if (context.ref === "refs/heads/main") {
|
||||
return `["staging"]`
|
||||
}
|
||||
if (context.ref === "refs/heads/production") {
|
||||
return `["prod-eu", "prod-us"]`
|
||||
}
|
||||
}
|
||||
return "[]"
|
||||
result-encoding: string
|
||||
- name: Print environments to build
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
environments: ${{ steps.affected-environments.outputs.result }}
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
console.log('Environments', `${process.env.environments}` ?? 'n/a');
|
||||
|
||||
ecs-deploy:
|
||||
uses: ./.github/workflows/_deploy_ecs_service.yml
|
||||
needs: [affected-services, affected-environments]
|
||||
secrets: inherit
|
||||
strategy:
|
||||
matrix:
|
||||
service: ${{ fromJson(needs.affected-services.outputs.services) }}
|
||||
environment: ${{ fromJson(needs.affected-environments.outputs.environments) }}
|
||||
with:
|
||||
service: ${{ matrix.service }}
|
||||
environment: ${{ matrix.environment }}
|
||||
@@ -14,10 +14,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@@ -35,24 +35,22 @@ jobs:
|
||||
run: pnpm run lint
|
||||
|
||||
test-docker-build:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
uses: actions/checkout@v4
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN_READ }}
|
||||
- name: Set NEXT_PUBLIC_BUILD_ID
|
||||
run: echo "NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
- name: Build and run both images from compose
|
||||
run: |
|
||||
docker compose -f docker-compose.build.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
|
||||
- name: Check server health
|
||||
run: |
|
||||
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
|
||||
|
||||
- name: Check worker health
|
||||
run: |
|
||||
timeout 10 bash -c 'until curl -f http://localhost:3030/api/health; do sleep 2; done'
|
||||
|
||||
- name: Ensure no unhealthy status
|
||||
run: |
|
||||
if docker-compose ps | grep "(unhealthy)"; then
|
||||
@@ -61,6 +59,12 @@ jobs:
|
||||
else
|
||||
echo "All services are healthy"
|
||||
fi
|
||||
- name: Check worker health
|
||||
run: |
|
||||
timeout 10 bash -c 'until curl -f http://localhost:3030/api/health; do sleep 2; done'
|
||||
- name: Check server health
|
||||
run: |
|
||||
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
|
||||
|
||||
tests-web:
|
||||
timeout-minutes: 20
|
||||
@@ -75,27 +79,33 @@ jobs:
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.5.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN_READ }}
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
grep -v -e '^REDIS_HOST=' .env.dev.example > .env
|
||||
|
||||
grep -v -e '^S3_BUCKET_NAME=' -e '^REDIS_HOST=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev.example > .env
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
@@ -103,18 +113,24 @@ jobs:
|
||||
docker compose ps
|
||||
env:
|
||||
POSTGRES_VERSION: ${{ matrix.postgres-version }}
|
||||
|
||||
- name: Seed DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm run db:seed
|
||||
|
||||
pnpm --filter=shared ch:up
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Start Langfuse
|
||||
run: (pnpm run start&)
|
||||
|
||||
env:
|
||||
LANGFUSE_INIT_ORG_ID: "seed-org-id"
|
||||
LANGFUSE_INIT_ORG_NAME: "Seed Org"
|
||||
LANGFUSE_INIT_PROJECT_ID: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"
|
||||
LANGFUSE_INIT_PROJECT_NAME: "Seed Project"
|
||||
LANGFUSE_INIT_PROJECT_PUBLIC_KEY: "pk-lf-1234567890"
|
||||
LANGFUSE_INIT_PROJECT_SECRET_KEY: "sk-lf-1234567890"
|
||||
LANGFUSE_INIT_USER_EMAIL: "demo@langfuse.com"
|
||||
LANGFUSE_INIT_USER_NAME: "Demo User"
|
||||
LANGFUSE_INIT_USER_PASSWORD: "password"
|
||||
- name: run tests
|
||||
run: pnpm --filter=web run test
|
||||
|
||||
@@ -131,41 +147,39 @@ jobs:
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.5.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN_READ }}
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
cp .env.dev.example web/.env
|
||||
cp .env.dev.example worker/.env
|
||||
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
docker compose ps
|
||||
|
||||
- name: Ensure no unhealthy status
|
||||
run: |
|
||||
if docker compose ps | grep "(unhealthy)"; then
|
||||
@@ -174,101 +188,95 @@ jobs:
|
||||
else
|
||||
echo "All services are healthy"
|
||||
fi
|
||||
|
||||
- name: Seed DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm run db:seed
|
||||
pnpm run --filter=shared ch:up
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter=worker... run build
|
||||
|
||||
- name: run tests
|
||||
run: pnpm --filter=worker run test
|
||||
|
||||
e2e-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN_READ }}
|
||||
- name: install dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
cp .env.dev.example web/.env
|
||||
|
||||
- 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: Seed DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm run db:seed
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Install playwright
|
||||
run: pnpm --filter=web exec playwright install --with-deps
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pnpm --filter=web run test:e2e
|
||||
|
||||
e2e-server-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN_READ }}
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
echo "LANGFUSE_ASYNC_INGESTION_PROCESSING=true" >> .env
|
||||
echo "LANGFUSE_CACHE_API_KEY_ENABLED=true" >> .env
|
||||
echo "LANGFUSE_CACHE_PROMPT_ENABLED=true" >> .env
|
||||
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
docker compose ps
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
|
||||
- name: Seed DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm run db:seed:examples
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Run server
|
||||
run: (pnpm run start&)
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pnpm --filter=web run test:e2e:server
|
||||
|
||||
@@ -309,35 +317,31 @@ jobs:
|
||||
with:
|
||||
version: 9.5.0
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
uses: actions/checkout@v4
|
||||
- name: Set NEXT_PUBLIC_BUILD_ID
|
||||
run: echo "NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
- name: Log in to the GitHub Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta-web
|
||||
uses: docker/metadata-action@v4
|
||||
@@ -352,7 +356,6 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push Docker image (web)
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
@@ -364,7 +367,6 @@ jobs:
|
||||
platforms: |
|
||||
linux/amd64
|
||||
${{ startsWith(github.ref, 'refs/tags/') && 'linux/arm64' || '' }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta-worker
|
||||
uses: docker/metadata-action@v4
|
||||
@@ -379,7 +381,6 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push Docker image (worker)
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"on":
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
name: Deploy to web
|
||||
jobs:
|
||||
porter-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Github tag
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Setup porter
|
||||
uses: porter-dev/setup-porter@v0.1.0
|
||||
- name: Deploy stack
|
||||
timeout-minutes: 30
|
||||
run: exec porter apply
|
||||
env:
|
||||
PORTER_CLUSTER: "4060"
|
||||
PORTER_DEPLOYMENT_TARGET_ID: 3be075c0-b121-4123-aec1-5dc571626b0c
|
||||
PORTER_HOST: https://dashboard.porter.run
|
||||
PORTER_PR_NUMBER: ${{ github.event.number }}
|
||||
PORTER_PROJECT: "12565"
|
||||
PORTER_REPO_NAME: ${{ github.event.repository.name }}
|
||||
PORTER_STACK_NAME: web
|
||||
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
|
||||
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4060 }}
|
||||
@@ -1,29 +0,0 @@
|
||||
"on":
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
name: Deploy to web (Staging)
|
||||
jobs:
|
||||
porter-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Github tag
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Setup porter
|
||||
uses: porter-dev/setup-porter@v0.1.0
|
||||
- name: Deploy stack
|
||||
timeout-minutes: 30
|
||||
run: exec porter apply
|
||||
env:
|
||||
PORTER_CLUSTER: "4037"
|
||||
PORTER_DEPLOYMENT_TARGET_ID: c22d8fa2-f97c-4e1f-9cae-83cc6bfbad35
|
||||
PORTER_HOST: https://dashboard.getporter.dev
|
||||
PORTER_PR_NUMBER: ${{ github.event.number }}
|
||||
PORTER_PROJECT: "12565"
|
||||
PORTER_REPO_NAME: ${{ github.event.repository.name }}
|
||||
PORTER_STACK_NAME: web
|
||||
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
|
||||
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4037 }}
|
||||
@@ -1,29 +0,0 @@
|
||||
"on":
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
name: Deploy to web (US temp)
|
||||
jobs:
|
||||
porter-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Github tag
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Setup porter
|
||||
uses: porter-dev/setup-porter@v0.1.0
|
||||
- name: Deploy stack
|
||||
timeout-minutes: 30
|
||||
run: exec porter apply
|
||||
env:
|
||||
PORTER_CLUSTER: "4054"
|
||||
PORTER_DEPLOYMENT_TARGET_ID: cddc0f2a-bb19-401a-9819-29f59f76b101
|
||||
PORTER_HOST: https://dashboard.porter.run
|
||||
PORTER_PR_NUMBER: ${{ github.event.number }}
|
||||
PORTER_PROJECT: "12565"
|
||||
PORTER_REPO_NAME: ${{ github.event.repository.name }}
|
||||
PORTER_STACK_NAME: web
|
||||
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
|
||||
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4054 }}
|
||||
@@ -1,29 +0,0 @@
|
||||
"on":
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
name: Deploy to worker (EU temp)
|
||||
jobs:
|
||||
porter-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Github tag
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Setup porter
|
||||
uses: porter-dev/setup-porter@v0.1.0
|
||||
- name: Deploy stack
|
||||
timeout-minutes: 30
|
||||
run: exec porter apply
|
||||
env:
|
||||
PORTER_CLUSTER: "4060"
|
||||
PORTER_DEPLOYMENT_TARGET_ID: 3be075c0-b121-4123-aec1-5dc571626b0c
|
||||
PORTER_HOST: https://dashboard.porter.run
|
||||
PORTER_PR_NUMBER: ${{ github.event.number }}
|
||||
PORTER_PROJECT: "12565"
|
||||
PORTER_REPO_NAME: ${{ github.event.repository.name }}
|
||||
PORTER_STACK_NAME: worker
|
||||
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
|
||||
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4060 }}
|
||||
@@ -1,29 +0,0 @@
|
||||
"on":
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
name: Deploy to worker (staging)
|
||||
jobs:
|
||||
porter-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Github tag
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Setup porter
|
||||
uses: porter-dev/setup-porter@v0.1.0
|
||||
- name: Deploy stack
|
||||
timeout-minutes: 30
|
||||
run: exec porter apply
|
||||
env:
|
||||
PORTER_CLUSTER: "4037"
|
||||
PORTER_DEPLOYMENT_TARGET_ID: c22d8fa2-f97c-4e1f-9cae-83cc6bfbad35
|
||||
PORTER_HOST: https://dashboard.getporter.dev
|
||||
PORTER_PR_NUMBER: ${{ github.event.number }}
|
||||
PORTER_PROJECT: "12565"
|
||||
PORTER_REPO_NAME: ${{ github.event.repository.name }}
|
||||
PORTER_STACK_NAME: worker
|
||||
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
|
||||
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4037 }}
|
||||
@@ -1,29 +0,0 @@
|
||||
"on":
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
name: Deploy to worker (US temp)
|
||||
jobs:
|
||||
porter-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Github tag
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Setup porter
|
||||
uses: porter-dev/setup-porter@v0.1.0
|
||||
- name: Deploy stack
|
||||
timeout-minutes: 30
|
||||
run: exec porter apply
|
||||
env:
|
||||
PORTER_CLUSTER: "4054"
|
||||
PORTER_DEPLOYMENT_TARGET_ID: cddc0f2a-bb19-401a-9819-29f59f76b101
|
||||
PORTER_HOST: https://dashboard.porter.run
|
||||
PORTER_PR_NUMBER: ${{ github.event.number }}
|
||||
PORTER_PROJECT: "12565"
|
||||
PORTER_REPO_NAME: ${{ github.event.repository.name }}
|
||||
PORTER_STACK_NAME: worker
|
||||
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
|
||||
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4054 }}
|
||||
Vendored
+2
-2
@@ -2,10 +2,10 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Next.js: debug server-side",
|
||||
"name": "Web and Worker: server side",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "npm run dev"
|
||||
"command": "pnpm run dev"
|
||||
},
|
||||
{
|
||||
"name": "Next.js: debug client-side",
|
||||
|
||||
Vendored
+1
@@ -20,6 +20,7 @@
|
||||
"prettier.documentSelectors": [
|
||||
"**/*.{cjs,mjs,ts,tsx,astro,md,mdx,json,yaml,yml}"
|
||||
],
|
||||
"prettier.trailingComma": "all",
|
||||
"mdx.experimentalLanguageServer": true,
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"docwriter.style": "JSDoc",
|
||||
|
||||
@@ -432,6 +432,20 @@ Example:
|
||||
op run --env-file="./.env" -- pnpm --filter=shared run db:deploy
|
||||
```
|
||||
|
||||
### Editing default models and prices
|
||||
|
||||
You can update the default AI models and prices by adding or updating an entry in `worker/src/constants/default-model-prices.json`.
|
||||
|
||||
Please note that
|
||||
|
||||
- prices are in USD
|
||||
- the list is ordered by ID, so make sure to keep this order
|
||||
- the `updated_at` field must be updated with the current date in ISO 8601 format. Otherwise, the change will be ignored.
|
||||
|
||||
### Transition period until V3 release
|
||||
|
||||
Until the V3 release, both the JSON record must be updated **and** a migration must be created to continue supporting self-hosted users. Note that the migration must updated both the `models` as well as the `prices` table accordingly.
|
||||
|
||||
## License
|
||||
|
||||
Langfuse is MIT licensed, except for `ee/` folder. See [LICENSE](LICENSE) and [docs](https://langfuse.com/docs/open-source) for more details.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||

|
||||

|
||||
|
||||
<div align="center"><h1> Langfuse: Open Source LLM Observability & Engineering Platform</h4></div>
|
||||
<div align="center"><h2> Debug and improve your LLM app</h4></div>
|
||||
<div align="center"><h3> LLM Observability, Prompt Management, LLM Evaluations, Datasets, LLM Metrics and Prompt Playground</h4></div>
|
||||
<div align="center"><h1>Langfuse: Open Source LLM Engineering Platform</h1></div>
|
||||
<div align="center"><h4>LLM Observability, Prompt Management, LLM Evaluations,<br/>Datasets, LLM Metrics, and Prompt Playground</h4></div>
|
||||
|
||||
<div align="center">
|
||||
<div>
|
||||
@@ -28,15 +27,15 @@
|
||||
</div>
|
||||
<span>Langfuse uses <a href="https://github.com/orgs/langfuse/discussions"><strong>Github Discussions</strong></a> for Support and Feature Requests.</span>
|
||||
<br/>
|
||||
<span>We're hiring. <a href="https://langfuse.com/careers"><strong>Join us</strong></a> in Backend Engineering, Product Engineering, and Developer Relations.</span>
|
||||
<span>We're hiring. <a href="https://langfuse.com/careers"><strong>Join us</strong></a> in Product Engineering and Developer Relations.</span>
|
||||
<br/>
|
||||
<br/>
|
||||
<div>
|
||||
<a href="https://github.com/langfuse/langfuse/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-red.svg?style=flat-square" alt="MIT License"></a>
|
||||
<a href="https://www.ycombinator.com/companies/langfuse"><img src="https://img.shields.io/badge/Y%20Combinator-W23-orange?style=flat-square" alt="Y Combinator W23"></a>
|
||||
<a href="https://github.com/langfuse/langfuse/pkgs/container/langfuse"><img alt="Docker Image" src="https://img.shields.io/badge/docker-langfuse-blue?logo=Docker&logoColor=white&style=flat-square"></a>
|
||||
<a href="https://www.npmjs.com/package/langfuse"><img src="https://img.shields.io/npm/v/langfuse?style=flat-square&label=npm+langfuse" alt="langfuse npm package"></a>
|
||||
<a href="https://pypi.python.org/pypi/langfuse"><img src="https://img.shields.io/pypi/v/langfuse.svg?style=flat-square&label=pypi+langfuse" alt="langfuse Python package on PyPi"></a>
|
||||
<a href="https://pypi.python.org/pypi/langfuse"><img src="https://img.shields.io/pypi/dm/langfuse?style=flat-square&logo=python&logoColor=white&label=pypi%20langfuse&color=blue" alt="langfuse Python package on PyPi"></a>
|
||||
<a href="https://www.npmjs.com/package/langfuse"><img src="https://img.shields.io/npm/dm/langfuse?style=flat-square&logo=npm&logoColor=white&label=npm%20langfuse&color=blue" alt="langfuse npm package"></a>
|
||||
</div>
|
||||
</div>
|
||||
</br>
|
||||
@@ -93,10 +92,10 @@ docker compose up -d
|
||||
|
||||
### Self-host (docker)
|
||||
|
||||
Langfuse is simple to self-host and keep updated. It currently requires only a single docker container.
|
||||
Langfuse is simple to self-host and keep updated. It currently requires only a single docker container and a postgres database.
|
||||
[→ Self Hosting Instructions](https://langfuse.com/docs/deployment/self-host)
|
||||
|
||||
Templated deployments: [Railway, GCP Cloud Run, AWS Fargate, Kubernetes and others](https://langfuse.com/docs/deployment/self-host#platform-specific-information)
|
||||
Templated deployments: [Railway, GCP, AWS, Azure, Kubernetes and others](https://langfuse.com/docs/deployment/self-host#platform-specific-information)
|
||||
|
||||
## Get Started
|
||||
|
||||
@@ -108,31 +107,31 @@ You need a Langfuse public and secret key to get started. Sign up [here](https:/
|
||||
|
||||
Note: We recommend using our fully async, typed [SDKs](https://langfuse.com/docs/sdk) that allow you to instrument any LLM application with any underlying model. They are available in [Python (Decorators)](https://langfuse.com/docs/sdk/python) & [JS/TS](https://langfuse.com/docs/sdk/typescript). The SDKs will always be the most fully featured and stable way to ingest data into Langfuse.
|
||||
|
||||
You may want to use another integration to get started quickly or implement a use case that we do not yet support. However, we recommend to migrate to the Langfuse SDKs over time to ensure performance and stability.
|
||||
|
||||
See the [→ Quickstart](https://langfuse.com/docs/get-started) to integrate Langfuse.
|
||||
|
||||
### LLM Observability Integrations
|
||||
|
||||
| Integration | Supports | Description |
|
||||
| -------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [SDK](/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
|
||||
| [OpenAI](/docs/integrations/openai) | Python, JS/TS | Automated instrumentation using drop-in replacement of OpenAI SDK. |
|
||||
| [Langchain](/docs/integrations/langchain) | Python, JS/TS | Automated instrumentation by passing callback handler to Langchain application. |
|
||||
| [LlamaIndex](/docs/integrations/llama-index/get-started) | Python | Automated instrumentation via LlamaIndex callback system. |
|
||||
| [Haystack](/docs/integrations/haystack) | Python | Automated instrumentation via Haystack content tracing system. |
|
||||
| [LiteLLM](/docs/integrations/litellm) | Python, JS/TS (proxy only) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
|
||||
| [API](/docs/api) | | Directly call the public API. OpenAPI spec available. |
|
||||
| Integration | Supports | Description |
|
||||
| ---------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [SDK](https://langfuse.com/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
|
||||
| [OpenAI](https://langfuse.com/docs/integrations/openai) | Python, JS/TS | Automated instrumentation using drop-in replacement of OpenAI SDK. |
|
||||
| [Langchain](https://langfuse.com/docs/integrations/langchain) | Python, JS/TS | Automated instrumentation by passing callback handler to Langchain application. |
|
||||
| [LlamaIndex](https://langfuse.com/docs/integrations/llama-index/get-started) | Python | Automated instrumentation via LlamaIndex callback system. |
|
||||
| [Haystack](https://langfuse.com/docs/integrations/haystack) | Python | Automated instrumentation via Haystack content tracing system. |
|
||||
| [LiteLLM](https://langfuse.com/docs/integrations/litellm) | Python, JS/TS (proxy only) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
|
||||
| [Vercel AI SDK](https://langfuse.com/docs/integrations/vercel-ai-sdk) | JS/TS | TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js. |
|
||||
| [API](https://langfuse.com/docs/api) | | Directly call the public API. OpenAPI spec available. |
|
||||
|
||||
Packages that integrate with Langfuse:
|
||||
Packages integrated with Langfuse:
|
||||
|
||||
| Name | Description |
|
||||
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Instructor](/docs/integrations/instructor) | Library to get structured LLM outputs (JSON, Pydantic) |
|
||||
| [Mirascope](/docs/integrations/mirascope) | Python toolkit for building LLM applications. |
|
||||
| [AI SDK by Vercel](/docs/sdk/typescript/example-vercel-ai) | Typescript SDK that makes streaming LLM outputs super easy. |
|
||||
| [Flowise](/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
|
||||
| [Langflow](/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
|
||||
| Name | Description |
|
||||
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Instructor](https://langfuse.com/docs/integrations/instructor) | Library to get structured LLM outputs (JSON, Pydantic) |
|
||||
| [Dify](https://langfuse.com/docs/integrations/dify) | Open source LLM app development platform with no-code builder. |
|
||||
| [Ollama](https://langfuse.com/docs/integrations/ollama) | Easily run open source LLMs on your own machine. |
|
||||
| [Mirascope](https://langfuse.com/docs/integrations/mirascope) | Python toolkit for building LLM applications. |
|
||||
| [Flowise](https://langfuse.com/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
|
||||
| [Langflow](https://langfuse.com/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
|
||||
|
||||
## Questions and feedback
|
||||
|
||||
@@ -140,13 +139,13 @@ Packages that integrate with Langfuse:
|
||||
|
||||
- [Roadmap](https://langfuse.com/roadmap)
|
||||
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions)
|
||||
- [Feature Requests](https://langfuse.com/idea
|
||||
- [Feature Requests](https://langfuse.com/ideas)
|
||||
|
||||
### Support and feedback
|
||||
|
||||
In order of preference the best way to communicate with us:
|
||||
|
||||
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions): Contribute [ideas](https://langfuse.com/idea) [support requests](https://github.com/orgs/langfuse/discussions/categories/support) and [report bugs](https://github.com/langfuse/langfuse/issues/new?labels=%F0%9F%90%9E%E2%9D%94+unconfirmed+bug&projects=&template=bug_report.yml&title=bug%3A+) (preferred as we create a permanent, indexed artifact for other community members)
|
||||
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions) (preferred): Contribute [ideas](https://langfuse.com/ideas), [support requests](https://langfuse.com/gh-support) and [report bugs](https://langfuse.com/issues)
|
||||
- [Discord](https://langfuse.com/discord): community support
|
||||
- Privately: contact at langfuse dot com
|
||||
|
||||
@@ -182,3 +181,13 @@ This helps us to:
|
||||
None of the data is shared with third parties and does not include any sensitive information. We want to be super transparent about this and you can find the exact data we collect [here](/web/src/features/telemetry/index.ts).
|
||||
|
||||
You can opt-out by setting `TELEMETRY_ENABLED=false`.
|
||||
|
||||
### Star History
|
||||
|
||||
<a href="https://star-history.com/#langfuse/langfuse&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=langfuse/langfuse&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=langfuse/langfuse&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=langfuse/langfuse&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
@@ -20,8 +20,15 @@ services:
|
||||
- NEXTAUTH_URL=http://localhost:3000
|
||||
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true}
|
||||
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
|
||||
- LANGFUSE_WORKER_HOST=${LANGFUSE_WORKER_HOST:-worker}
|
||||
- LANGFUSE_WORKER_PASSWORD=${LANGFUSE_WORKER_PASSWORD:-mybasicauthsecret}
|
||||
- LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-}
|
||||
- LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-}
|
||||
- LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-}
|
||||
- LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-}
|
||||
- LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
|
||||
- LANGFUSE_INIT_PROJECT_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
|
||||
- LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-}
|
||||
- LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-}
|
||||
- LANGFUSE_INIT_USER_PASSWORD=${LANGFUSE_INIT_USER_PASSWORD:-}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/public/health"]
|
||||
@@ -49,7 +56,6 @@ services:
|
||||
- REDIS_HOST=${REDIS_HOST:-redis}
|
||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||
- REDIS_AUTH=${REDIS_AUTH:-myredissecret}
|
||||
- LANGFUSE_WORKER_PASSWORD=${LANGFUSE_WORKER_PASSWORD:-mybasicauthsecret}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3030/api/health"]
|
||||
|
||||
@@ -17,6 +17,27 @@ services:
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
container_name: minio
|
||||
entrypoint: sh
|
||||
# create the 'langfuse' bucket before starting the service
|
||||
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: minio
|
||||
MINIO_SECRET_KEY: miniosecret
|
||||
ports:
|
||||
- "9090:9000"
|
||||
- "9091:9001"
|
||||
volumes:
|
||||
- langfuse_minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 1s
|
||||
|
||||
redis:
|
||||
image: redis:7.2.4
|
||||
restart: always
|
||||
@@ -28,6 +49,11 @@ services:
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
command: ["postgres", "-c", "log_statement=all"]
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
@@ -45,3 +71,5 @@ volumes:
|
||||
driver: local
|
||||
langfuse_clickhouse_logs:
|
||||
driver: local
|
||||
langfuse_minio_data:
|
||||
driver: local
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
services:
|
||||
langfuse-worker:
|
||||
image: langfuse/langfuse-worker:latest
|
||||
depends_on: &langfuse-depends-on
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3030:3030"
|
||||
environment: &langfuse-worker-env
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
|
||||
SALT: "mysalt"
|
||||
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # generate via `openssl rand -hex 32`
|
||||
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
|
||||
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-true}
|
||||
LANGFUSE_ASYNC_INGESTION_PROCESSING: ${LANGFUSE_ASYNC_INGESTION_PROCESSING:-true}
|
||||
LANGFUSE_ASYNC_CLICKHOUSE_INGESTION_PROCESSING: ${LANGFUSE_ASYNC_CLICKHOUSE_INGESTION_PROCESSING:-true}
|
||||
CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123}
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ENABLED: ${LANGFUSE_S3_EVENT_UPLOAD_ENABLED:-true}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-us-east-1}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true}
|
||||
REDIS_HOST: ${REDIS_HOST:-redis}
|
||||
REDIS_PORT: ${REDIS_PORT:-6379}
|
||||
REDIS_AUTH: ${REDIS_AUTH:-myredissecret}
|
||||
|
||||
langfuse-web:
|
||||
image: langfuse/langfuse:latest
|
||||
depends_on: *langfuse-depends-on
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
<<: *langfuse-worker-env
|
||||
NEXTAUTH_URL: http://localhost:3000
|
||||
NEXTAUTH_SECRET: mysecret
|
||||
LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
|
||||
LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-}
|
||||
LANGFUSE_INIT_PROJECT_ID: ${LANGFUSE_INIT_PROJECT_ID:-}
|
||||
LANGFUSE_INIT_PROJECT_NAME: ${LANGFUSE_INIT_PROJECT_NAME:-}
|
||||
LANGFUSE_INIT_PROJECT_PUBLIC_KEY: ${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
|
||||
LANGFUSE_INIT_PROJECT_SECRET_KEY: ${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
|
||||
LANGFUSE_INIT_USER_EMAIL: ${LANGFUSE_INIT_USER_EMAIL:-}
|
||||
LANGFUSE_INIT_USER_NAME: ${LANGFUSE_INIT_USER_NAME:-}
|
||||
LANGFUSE_INIT_USER_PASSWORD: ${LANGFUSE_INIT_USER_PASSWORD:-}
|
||||
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server
|
||||
user: "101:101"
|
||||
container_name: clickhouse
|
||||
hostname: clickhouse
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
CLICKHOUSE_USER: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse
|
||||
volumes:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
ports:
|
||||
- "8123:8123"
|
||||
- "9000:9000"
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
container_name: minio
|
||||
entrypoint: sh
|
||||
# create the 'langfuse' bucket before starting the service
|
||||
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
||||
environment:
|
||||
MINIO_ROOT_USER: minio
|
||||
MINIO_ROOT_PASSWORD: miniosecret
|
||||
ports:
|
||||
- "9090:9000"
|
||||
- "9091:9001"
|
||||
volumes:
|
||||
- langfuse_minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 1s
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
restart: always
|
||||
command: >
|
||||
--requirepass ${REDIS_AUTH:-myredissecret}
|
||||
ports:
|
||||
- 6379:6379
|
||||
healthcheck:
|
||||
test: [ 'CMD', 'redis-cli', 'ping' ]
|
||||
interval: 3s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- langfuse_postgres_data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
langfuse_postgres_data:
|
||||
driver: local
|
||||
langfuse_clickhouse_data:
|
||||
driver: local
|
||||
langfuse_clickhouse_logs:
|
||||
driver: local
|
||||
langfuse_minio_data:
|
||||
driver: local
|
||||
@@ -14,6 +14,15 @@ services:
|
||||
- NEXTAUTH_URL=http://localhost:3000
|
||||
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true}
|
||||
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
|
||||
- LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-}
|
||||
- LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-}
|
||||
- LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-}
|
||||
- LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-}
|
||||
- LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
|
||||
- LANGFUSE_INIT_PROJECT_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
|
||||
- LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-}
|
||||
- LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-}
|
||||
- LANGFUSE_INIT_USER_PASSWORD=${LANGFUSE_INIT_USER_PASSWORD:-}
|
||||
|
||||
db:
|
||||
image: postgres
|
||||
|
||||
+9
-3
@@ -25,8 +25,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@langfuse/shared": "workspace:*",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.9.0",
|
||||
"next": "^14.2.6",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"axios": "^1.7.7",
|
||||
"next": "^14.2.15",
|
||||
"next-auth": "^4.24.7",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
@@ -39,9 +40,14 @@
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.2.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"jsonpath-plus": "10.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120
|
||||
}
|
||||
+5
-2
@@ -1,9 +1,12 @@
|
||||
import { z } from "zod";
|
||||
import { env as sharedEnv } from "@langfuse/shared";
|
||||
import { env as sharedEnv, removeEmptyEnvVariables } from "@langfuse/shared";
|
||||
|
||||
const EnvSchema = z.object({
|
||||
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION: z.string().optional(),
|
||||
LANGFUSE_EE_LICENSE_KEY: z.string().optional(),
|
||||
});
|
||||
|
||||
export const env = { ...sharedEnv, ...EnvSchema.parse(process.env) };
|
||||
export const env = {
|
||||
...sharedEnv,
|
||||
...EnvSchema.parse(removeEmptyEnvVariables(process.env)),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
pagination: ./utils/pagination.yml
|
||||
commons: ./commons.yml
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public
|
||||
endpoints:
|
||||
create:
|
||||
docs: Create a comment. Comments may be attached to different object types (trace, observation, session, prompt).
|
||||
method: POST
|
||||
path: /comments
|
||||
request: CreateCommentRequest
|
||||
response: CreateCommentResponse
|
||||
get:
|
||||
docs: Get all comments
|
||||
method: GET
|
||||
path: /comments
|
||||
request:
|
||||
name: GetCommentsRequest
|
||||
query-parameters:
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: Page number, starts at 1.
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit
|
||||
objectType:
|
||||
type: optional<string>
|
||||
docs: Filter comments by object type (trace, observation, session, prompt).
|
||||
objectId:
|
||||
type: optional<string>
|
||||
docs: Filter comments by object id. If objectType is not provided, an error will be thrown.
|
||||
authorUserId:
|
||||
type: optional<string>
|
||||
docs: Filter comments by author user id.
|
||||
response: GetCommentsResponse
|
||||
get-by-id:
|
||||
docs: Get a comment by id
|
||||
method: GET
|
||||
path: /comments/{commentId}
|
||||
path-parameters:
|
||||
commentId:
|
||||
type: string
|
||||
docs: The unique langfuse identifier of a comment
|
||||
response: commons.Comment
|
||||
types:
|
||||
CreateCommentRequest:
|
||||
properties:
|
||||
projectId:
|
||||
type: string
|
||||
docs: The id of the project to attach the comment to.
|
||||
objectType:
|
||||
type: string
|
||||
docs: The type of the object to attach the comment to (trace, observation, session, prompt).
|
||||
objectId:
|
||||
type: string
|
||||
docs: The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown.
|
||||
content:
|
||||
type: string
|
||||
docs: The content of the comment. May include markdown. Currently limited to 500 characters.
|
||||
authorUserId:
|
||||
type: optional<string>
|
||||
docs: The id of the user who created the comment.
|
||||
CreateCommentResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
docs: The id of the created object in Langfuse
|
||||
GetCommentsResponse:
|
||||
properties:
|
||||
data: list<commons.Comment>
|
||||
meta: pagination.MetaResponse
|
||||
@@ -240,6 +240,9 @@ types:
|
||||
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
|
||||
queueId:
|
||||
type: optional<string>
|
||||
docs: Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue.
|
||||
NumericScore:
|
||||
extends: BaseScore
|
||||
properties:
|
||||
@@ -283,6 +286,18 @@ types:
|
||||
- double
|
||||
- string
|
||||
docs: The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores
|
||||
|
||||
Comment:
|
||||
properties:
|
||||
id: string
|
||||
projectId: string
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
objectType: CommentObjectType
|
||||
objectId: string
|
||||
content: string
|
||||
authorUserId: optional<string>
|
||||
|
||||
Dataset:
|
||||
properties:
|
||||
id: string
|
||||
@@ -402,6 +417,12 @@ types:
|
||||
- optional<integer>
|
||||
- optional<boolean>
|
||||
- optional<list<string>>
|
||||
CommentObjectType:
|
||||
enum:
|
||||
- TRACE
|
||||
- OBSERVATION
|
||||
- SESSION
|
||||
- PROMPT
|
||||
DatasetStatus:
|
||||
enum:
|
||||
- ACTIVE
|
||||
|
||||
@@ -31,10 +31,10 @@ service:
|
||||
docs: Optional filter for metrics where traces include all of these tags
|
||||
fromTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Optional filter to only include traces on or after a certain datetime (ISO 8601)
|
||||
docs: Optional filter to only include traces and observations on or after a certain datetime (ISO 8601)
|
||||
toTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Optional filter to only include traces before a certain datetime (ISO 8601)
|
||||
docs: Optional filter to only include traces and observations before a certain datetime (ISO 8601)
|
||||
response: DailyMetrics
|
||||
types:
|
||||
DailyMetrics:
|
||||
|
||||
@@ -55,7 +55,7 @@ types:
|
||||
type: string
|
||||
startDate:
|
||||
docs: Apply only to generations which are newer than this ISO date.
|
||||
type: optional<date>
|
||||
type: optional<datetime>
|
||||
unit:
|
||||
docs: Unit used by this model.
|
||||
type: commons.ModelUsageUnit
|
||||
|
||||
@@ -52,10 +52,17 @@ service:
|
||||
configId:
|
||||
type: optional<string>
|
||||
docs: Retrieve only scores with a specific configId.
|
||||
queueId:
|
||||
type: optional<string>
|
||||
docs: Retrieve only scores with a specific annotation queueId.
|
||||
dataType:
|
||||
type: optional<commons.ScoreDataType>
|
||||
docs: Retrieve only scores with a specific dataType.
|
||||
response: Scores
|
||||
traceTags:
|
||||
type: optional<list<string>>
|
||||
allow-multiple: true
|
||||
docs: Only scores linked to traces that include all of these tags will be returned.
|
||||
response: GetScoresResponse
|
||||
get-by-id:
|
||||
docs: Get a score
|
||||
method: GET
|
||||
@@ -132,7 +139,19 @@ types:
|
||||
id:
|
||||
type: string
|
||||
docs: The id of the created object in Langfuse
|
||||
Scores:
|
||||
GetScoresResponseTraceData:
|
||||
properties:
|
||||
data: list<commons.Score>
|
||||
userId:
|
||||
type: optional<string>
|
||||
docs: The user ID associated with the trace referenced by score
|
||||
tags:
|
||||
type: optional<list<string>>
|
||||
docs: A list of tags associated with the trace referenced by score
|
||||
GetScoresResponseData:
|
||||
properties:
|
||||
<<: commons.Score
|
||||
trace: GetScoresResponseTraceData
|
||||
GetScoresResponse:
|
||||
properties:
|
||||
data: list<GetScoresResponseData>
|
||||
meta: pagination.MetaResponse
|
||||
|
||||
@@ -3,32 +3,22 @@ groups:
|
||||
local:
|
||||
generators:
|
||||
- name: fernapi/fern-openapi
|
||||
version: 0.0.28
|
||||
version: 0.0.31
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../web/public/generated/api
|
||||
config:
|
||||
namespaceExport: Langfuse
|
||||
allowCustomFetcher: true
|
||||
- name: fernapi/fern-python-sdk
|
||||
version: 1.1.0-rc2
|
||||
version: 2.16.0
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../generated/python
|
||||
config:
|
||||
client_class_name: FernLangfuse
|
||||
improved_imports: false
|
||||
inline_request_params: false
|
||||
pydantic_config:
|
||||
require_optional_fields: false
|
||||
use_str_enums: false
|
||||
- name: fernapi/fern-typescript-node-sdk
|
||||
version: 0.7.1
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../generated/typescript-server
|
||||
config:
|
||||
namespaceExport: Langfuse
|
||||
allowCustomFetcher: true
|
||||
- name: fernapi/fern-postman
|
||||
version: 0.0.45
|
||||
output:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"organization": "finto",
|
||||
"version": "0.30.3"
|
||||
"version": "0.43.7"
|
||||
}
|
||||
+11
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langfuse",
|
||||
"version": "2.75.2",
|
||||
"version": "2.87.0",
|
||||
"author": "engineering@langfuse.com",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@@ -9,15 +9,16 @@
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"infra:dev:up": "docker compose -f ./docker-compose.dev.yml up -d",
|
||||
"infra:dev:up": "docker compose -f ./docker-compose.dev.yml up -d --wait",
|
||||
"infra:dev:down": "docker compose -f ./docker-compose.dev.yml down",
|
||||
"infra:dev:prune": "docker compose -f ./docker-compose.dev.yml down -v",
|
||||
"db:generate": "turbo run db:generate",
|
||||
"db:migrate": "turbo run db:migrate",
|
||||
"db:seed": "turbo run db:seed",
|
||||
"db:seed:examples": "turbo run db:seed:examples",
|
||||
"nuke": "bash ./scripts/nuke.sh",
|
||||
"dx": "pnpm i && pnpm run infra:dev:up && pnpm --filter=shared run db:reset && pnpm --filter=shared run ch:reset && pnpm --filter=shared run db:seed:examples && pnpm run dev",
|
||||
"dx-f": "pnpm i && pnpm run infra:dev:up && pnpm --filter=shared run db:reset -f && pnpm --filter=shared run ch:reset && pnpm --filter=shared run db:seed:examples && pnpm run dev",
|
||||
"dx": "pnpm i && pnpm run infra:dev:prune && pnpm run infra:dev:up --pull always && pnpm --filter=shared run db:reset && pnpm --filter=shared run ch:reset && pnpm --filter=shared run db:seed:examples && pnpm run dev",
|
||||
"dx-f": "pnpm i && pnpm run infra:dev:prune && pnpm run infra:dev:up --pull always && pnpm --filter=shared run db:reset -f && pnpm --filter=shared run ch:reset && pnpm --filter=shared run db:seed:examples && pnpm run dev",
|
||||
"dx:skip-infra": "pnpm i && pnpm --filter=shared run db:reset && pnpm --filter=shared run ch:reset && pnpm --filter=shared run db:seed:examples && pnpm run dev",
|
||||
"build": "turbo run build",
|
||||
"start": "turbo run start",
|
||||
@@ -33,7 +34,7 @@
|
||||
"braces": "3.0.3",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"husky": "^9.0.11",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.3.3",
|
||||
"release-it": "^17.3.0",
|
||||
"turbo": "^1.13.4"
|
||||
},
|
||||
@@ -80,5 +81,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"jsonpath-plus": "10.0.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.5.0"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"@typescript-eslint/parser": "^7.12.0",
|
||||
"@vercel/style-guide": "^6.0.0",
|
||||
"eslint-config-next": "^14.2.6",
|
||||
"eslint-config-next": "^14.2.15",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "^1.13.4",
|
||||
"eslint-plugin-only-warn": "^1.1.0",
|
||||
|
||||
@@ -3,24 +3,30 @@ CREATE TABLE traces (
|
||||
`timestamp` DateTime64(3),
|
||||
`name` String,
|
||||
`user_id` Nullable(String),
|
||||
`metadata` Map(String, String) CODEC(ZSTD(1)),
|
||||
`metadata` Map(LowCardinality(String), String),
|
||||
`release` Nullable(String),
|
||||
`version` Nullable(String),
|
||||
`project_id` String,
|
||||
`public` Bool,
|
||||
`bookmarked` Bool,
|
||||
`tags` Array(String),
|
||||
`input` Nullable(String) CODEC(ZSTD(1)),
|
||||
`output` Nullable(String) CODEC(ZSTD(1)),
|
||||
`input` Nullable(String) CODEC(ZSTD(3)),
|
||||
`output` Nullable(String) CODEC(ZSTD(3)),
|
||||
`session_id` Nullable(String),
|
||||
`created_at` DateTime64(3) DEFAULT now(),
|
||||
`updated_at` DateTime64(3) DEFAULT now(),
|
||||
updated_at DateTime64(3) DEFAULT now(),
|
||||
`event_ts` DateTime64(3),
|
||||
`is_deleted` UInt8,
|
||||
INDEX idx_id id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_res_metadata_key mapKeys(metadata) TYPE bloom_filter(0.01) GRANULARITY 1,
|
||||
INDEX idx_res_metadata_value mapValues(metadata) TYPE bloom_filter(0.01) GRANULARITY 1
|
||||
) ENGINE = ReplacingMergeTree Partition by toYYYYMM(timestamp)
|
||||
) ENGINE = ReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(timestamp)
|
||||
PRIMARY KEY (
|
||||
project_id,
|
||||
toDate(timestamp)
|
||||
)
|
||||
ORDER BY (
|
||||
project_id,
|
||||
toUnixTimestamp(timestamp),
|
||||
toDate(timestamp),
|
||||
id
|
||||
);
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ CREATE TABLE observations (
|
||||
`start_time` DateTime64(3),
|
||||
`end_time` Nullable(DateTime64(3)),
|
||||
`name` String,
|
||||
`metadata` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
|
||||
`metadata` Map(LowCardinality(String), String),
|
||||
`level` LowCardinality(String),
|
||||
`status_message` Nullable(String),
|
||||
`version` Nullable(String),
|
||||
@@ -16,18 +16,10 @@ CREATE TABLE observations (
|
||||
`provided_model_name` Nullable(String),
|
||||
`internal_model_id` Nullable(String),
|
||||
`model_parameters` Nullable(String),
|
||||
`provided_input_usage_units` Nullable(Decimal64(12)),
|
||||
`provided_output_usage_units` Nullable(Decimal64(12)),
|
||||
`provided_total_usage_units` Nullable(Decimal64(12)),
|
||||
`input_usage_units` Nullable(Decimal64(12)),
|
||||
`output_usage_units` Nullable(Decimal64(12)),
|
||||
`total_usage_units` Nullable(Decimal64(12)),
|
||||
`unit` Nullable(String),
|
||||
`provided_input_cost` Nullable(Decimal64(12)),
|
||||
`provided_output_cost` Nullable(Decimal64(12)),
|
||||
`provided_total_cost` Nullable(Decimal64(12)),
|
||||
`input_cost` Nullable(Decimal64(12)),
|
||||
`output_cost` Nullable(Decimal64(12)),
|
||||
`provided_usage_details` Map(LowCardinality(String), UInt64),
|
||||
`usage_details` Map(LowCardinality(String), UInt64),
|
||||
`provided_cost_details` Map(LowCardinality(String), Decimal64(12)),
|
||||
`cost_details` Map(LowCardinality(String), Decimal64(12)),
|
||||
`total_cost` Nullable(Decimal64(12)),
|
||||
`completion_start_time` Nullable(DateTime64(3)),
|
||||
`prompt_id` Nullable(String),
|
||||
@@ -35,16 +27,21 @@ CREATE TABLE observations (
|
||||
`prompt_version` Nullable(UInt16),
|
||||
`created_at` DateTime64(3) DEFAULT now(),
|
||||
`updated_at` DateTime64(3) DEFAULT now(),
|
||||
event_ts DateTime64(3),
|
||||
is_deleted UInt8,
|
||||
INDEX idx_id id TYPE bloom_filter() GRANULARITY 1,
|
||||
INDEX idx_trace_id trace_id TYPE bloom_filter() GRANULARITY 1,
|
||||
INDEX idx_project_id project_id TYPE bloom_filter() GRANULARITY 1,
|
||||
INDEX idx_res_metadata_key mapKeys(metadata) TYPE bloom_filter() GRANULARITY 1,
|
||||
INDEX idx_res_metadata_value mapValues(metadata) TYPE bloom_filter() GRANULARITY 1
|
||||
) ENGINE = ReplacingMergeTree Partition by toYYYYMM(start_time)
|
||||
INDEX idx_project_id project_id TYPE bloom_filter() GRANULARITY 1
|
||||
) ENGINE = ReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(start_time)
|
||||
PRIMARY KEY (
|
||||
project_id,
|
||||
`type`,
|
||||
toDate(start_time)
|
||||
)
|
||||
ORDER BY (
|
||||
project_id,
|
||||
`type`,
|
||||
trace_id,
|
||||
toUnixTimestamp(start_time),
|
||||
toDate(start_time),
|
||||
id
|
||||
);
|
||||
);
|
||||
|
||||
|
||||
@@ -12,14 +12,22 @@ CREATE TABLE scores (
|
||||
`config_id` Nullable(String),
|
||||
`data_type` String,
|
||||
`string_value` Nullable(String),
|
||||
`queue_id` Nullable(String),
|
||||
`created_at` DateTime64(3) DEFAULT now(),
|
||||
`updated_at` DateTime64(3) DEFAULT now(),
|
||||
event_ts DateTime64(3),
|
||||
`is_deleted` UInt8,
|
||||
INDEX idx_id id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_project_id trace_id TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) ENGINE = ReplacingMergeTree Partition by toYYYYMM(timestamp)
|
||||
INDEX idx_project_trace_observation (project_id, trace_id, observation_id) TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) ENGINE = ReplacingMergeTree(event_ts, is_deleted) Partition by toYYYYMM(timestamp)
|
||||
PRIMARY KEY (
|
||||
project_id,
|
||||
toDate(timestamp),
|
||||
name
|
||||
)
|
||||
ORDER BY (
|
||||
project_id,
|
||||
trace_id,
|
||||
toUnixTimestamp(timestamp),
|
||||
toDate(timestamp),
|
||||
name,
|
||||
id
|
||||
);
|
||||
)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { clickhouseClient } from "@langfuse/shared/src/server";
|
||||
import { prisma } from "../../src/db";
|
||||
import { redis } from "@langfuse/shared/src/server";
|
||||
import { prepareClickhouse } from "../../scripts/prepareClickhouse";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const projectIds = [
|
||||
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
|
||||
"239ad00f-562f-411d-af14-831c75ddd875",
|
||||
]; // Example project IDs
|
||||
await prepareClickhouse(projectIds, {
|
||||
numberOfDays: 3,
|
||||
totalObservations: 1000,
|
||||
});
|
||||
|
||||
console.log("Clickhouse preparation completed successfully.");
|
||||
} catch (error) {
|
||||
console.error("Error during Clickhouse preparation:", error);
|
||||
} finally {
|
||||
await clickhouseClient.close();
|
||||
await prisma.$disconnect();
|
||||
redis?.disconnect();
|
||||
console.log("Disconnected from Clickhouse.");
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -21,6 +21,10 @@
|
||||
"import": "./dist/src/server/index.js",
|
||||
"require": "./dist/src/server/index.js"
|
||||
},
|
||||
"./src/server/auth/apiKeys": {
|
||||
"import": "./dist/src/server/auth/apiKeys.js",
|
||||
"require": "./dist/src/server/auth/apiKeys.js"
|
||||
},
|
||||
"./encryption": {
|
||||
"import": "./dist/src/encryption/index.js",
|
||||
"require": "./dist/src/encryption/index.js"
|
||||
@@ -43,67 +47,79 @@
|
||||
"ch:up": "bash clickhouse/scripts/up.sh",
|
||||
"ch:down": "bash clickhouse/scripts/down.sh",
|
||||
"ch:drop": "bash clickhouse/scripts/drop.sh",
|
||||
"ch:reset": "pnpm run ch:down && pnpm run ch:up"
|
||||
"ch:reset": "pnpm run ch:down && pnpm run ch:up && pnpm run ch:seed",
|
||||
"ch:seed": "dotenv -e ../../.env -- tsx clickhouse/scripts/seed.ts",
|
||||
"load:setup": "dotenv -e ../../.env -- tsx scripts/load-seed.ts"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node -r tsconfig-paths/register -r dotenv/config --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/tokenizer": "^0.0.4",
|
||||
"@aws-sdk/client-s3": "^3.627.0",
|
||||
"@aws-sdk/lib-storage": "^3.568.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.554.0",
|
||||
"@aws-sdk/client-cloudwatch": "^3.675.0",
|
||||
"@aws-sdk/client-s3": "^3.675.0",
|
||||
"@aws-sdk/lib-storage": "^3.675.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.679.0",
|
||||
"@clickhouse/client": "^1.4.0",
|
||||
"@langchain/anthropic": "^0.1.21",
|
||||
"@langchain/core": "^0.1.61",
|
||||
"@langchain/openai": "^0.0.33",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.9.0",
|
||||
"@prisma/client": "^5.17.0",
|
||||
"@langchain/anthropic": "^0.3.1",
|
||||
"@langchain/aws": "^0.1.0",
|
||||
"@langchain/core": "^0.3.9",
|
||||
"@langchain/openai": "^0.3.0",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"@prisma/client": "^5.20.0",
|
||||
"@react-email/components": "^0.0.19",
|
||||
"@react-email/render": "^0.0.15",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"axios": "^1.7.7",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bullmq": "^5.12.10",
|
||||
"dd-trace": "^5.19.0",
|
||||
"dd-trace": "^5.23.1",
|
||||
"decimal.js": "^10.4.3",
|
||||
"exponential-backoff": "^3.1.1",
|
||||
"ioredis": "^5.4.1",
|
||||
"kysely": "^0.27.4",
|
||||
"langchain": "^0.2.6",
|
||||
"langchain": "^0.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"next-auth": "^4.24.7",
|
||||
"nodemailer": "^6.9.13",
|
||||
"nodemailer": "^6.9.15",
|
||||
"prisma-extension-kysely": "^2.1.0",
|
||||
"uuid": "^9.0.1",
|
||||
"winston": "^3.15.0",
|
||||
"zod": "^3.23.8",
|
||||
"zod-to-json-schema": "^3.23.2"
|
||||
"zod-to-json-schema": "^3.23.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/node": "^20.11.29",
|
||||
"@types/nodemailer": "^6.4.15",
|
||||
"@types/pg": "^8.11.6",
|
||||
"@types/nodemailer": "^6.4.16",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/parser": "^7.12.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"kysely-codegen": "^0.11.0",
|
||||
"nodemon": "^3.1.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prisma": "^5.17.0",
|
||||
"kysely-codegen": "^0.16.8",
|
||||
"nodemon": "^3.1.7",
|
||||
"prettier": "^3.3.3",
|
||||
"prisma": "^5.20.0",
|
||||
"prisma-erd-generator": "^1.11.2",
|
||||
"prisma-kysely": "^1.8.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.2.0",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^1.5.3"
|
||||
"vitest": "^2.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.79",
|
||||
"react": "^18.0.0"
|
||||
"@types/react": "~18.2.79",
|
||||
"react": "~18.2.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"jsonpath-plus": "10.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120
|
||||
}
|
||||
@@ -37,6 +37,16 @@ export const ScoreDataType = {
|
||||
BOOLEAN: "BOOLEAN"
|
||||
} as const;
|
||||
export type ScoreDataType = (typeof ScoreDataType)[keyof typeof ScoreDataType];
|
||||
export const AnnotationQueueStatus = {
|
||||
PENDING: "PENDING",
|
||||
COMPLETED: "COMPLETED"
|
||||
} as const;
|
||||
export type AnnotationQueueStatus = (typeof AnnotationQueueStatus)[keyof typeof AnnotationQueueStatus];
|
||||
export const AnnotationQueueObjectType = {
|
||||
TRACE: "TRACE",
|
||||
OBSERVATION: "OBSERVATION"
|
||||
} as const;
|
||||
export type AnnotationQueueObjectType = (typeof AnnotationQueueObjectType)[keyof typeof AnnotationQueueObjectType];
|
||||
export const DatasetStatus = {
|
||||
ACTIVE: "ACTIVE",
|
||||
ARCHIVED: "ARCHIVED"
|
||||
@@ -80,6 +90,31 @@ export type Account = {
|
||||
scope: string | null;
|
||||
id_token: string | null;
|
||||
session_state: string | null;
|
||||
refresh_token_expires_in: number | null;
|
||||
created_at: number | null;
|
||||
};
|
||||
export type AnnotationQueue = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
score_config_ids: Generated<string[]>;
|
||||
project_id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type AnnotationQueueItem = {
|
||||
id: string;
|
||||
queue_id: string;
|
||||
object_id: string;
|
||||
object_type: AnnotationQueueObjectType;
|
||||
status: Generated<AnnotationQueueStatus>;
|
||||
locked_at: Timestamp | null;
|
||||
locked_by_user_id: string | null;
|
||||
annotator_user_id: string | null;
|
||||
completed_at: Timestamp | null;
|
||||
project_id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type ApiKey = {
|
||||
id: string;
|
||||
@@ -108,6 +143,17 @@ export type AuditLog = {
|
||||
before: string | null;
|
||||
after: string | null;
|
||||
};
|
||||
export type BackgroundMigration = {
|
||||
id: string;
|
||||
name: string;
|
||||
script: string;
|
||||
args: unknown;
|
||||
finished_at: Timestamp | null;
|
||||
failed_at: Timestamp | null;
|
||||
failed_reason: string | null;
|
||||
worker_id: string | null;
|
||||
locked_at: Timestamp | null;
|
||||
};
|
||||
export type BatchExport = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
@@ -244,6 +290,7 @@ export type LlmApiKeys = {
|
||||
base_url: string | null;
|
||||
custom_models: Generated<string[]>;
|
||||
with_default_models: Generated<boolean>;
|
||||
config: unknown | null;
|
||||
project_id: string;
|
||||
};
|
||||
export type MembershipInvitation = {
|
||||
@@ -268,7 +315,7 @@ export type Model = {
|
||||
input_price: string | null;
|
||||
output_price: string | null;
|
||||
total_price: string | null;
|
||||
unit: string;
|
||||
unit: string | null;
|
||||
tokenizer_id: string | null;
|
||||
tokenizer_config: unknown | null;
|
||||
};
|
||||
@@ -366,6 +413,14 @@ export type PosthogIntegration = {
|
||||
enabled: boolean;
|
||||
created_at: Generated<Timestamp>;
|
||||
};
|
||||
export type Price = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
model_id: string;
|
||||
usage_type: string;
|
||||
price: string;
|
||||
};
|
||||
export type Project = {
|
||||
id: string;
|
||||
org_id: string;
|
||||
@@ -409,6 +464,7 @@ export type Score = {
|
||||
observation_id: string | null;
|
||||
config_id: string | null;
|
||||
string_value: string | null;
|
||||
queue_id: string | null;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
data_type: Generated<ScoreDataType>;
|
||||
@@ -505,8 +561,11 @@ export type VerificationToken = {
|
||||
};
|
||||
export type DB = {
|
||||
Account: Account;
|
||||
annotation_queue_items: AnnotationQueueItem;
|
||||
annotation_queues: AnnotationQueue;
|
||||
api_keys: ApiKey;
|
||||
audit_logs: AuditLog;
|
||||
background_migrations: BackgroundMigration;
|
||||
batch_exports: BatchExport;
|
||||
comments: Comment;
|
||||
cron_jobs: CronJobs;
|
||||
@@ -526,6 +585,7 @@ export type DB = {
|
||||
organization_memberships: OrganizationMembership;
|
||||
organizations: Organization;
|
||||
posthog_integrations: PosthogIntegration;
|
||||
prices: Price;
|
||||
project_memberships: ProjectMembership;
|
||||
projects: Project;
|
||||
prompts: Prompt;
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
-- For OpenAI reasoning models, manually tokenizing output string does not account for reasoning tokens, thus would lead to incorrect cost
|
||||
-- So no manual tokenizer config is provided
|
||||
-- Reference: https://platform.openai.com/docs/guides/reasoning/how-reasoning-works
|
||||
|
||||
INSERT INTO models (
|
||||
id,
|
||||
project_id,
|
||||
model_name,
|
||||
match_pattern,
|
||||
start_date,
|
||||
input_price,
|
||||
output_price,
|
||||
total_price,
|
||||
unit,
|
||||
tokenizer_id,
|
||||
tokenizer_config
|
||||
)
|
||||
VALUES
|
||||
-- o1-preview
|
||||
('cm10ivcdp0000gix7lelmbw80', NULL, 'o1-preview', '(?i)^(o1-preview)$', NULL, 0.000015, 0.00006, NULL, 'TOKENS', NULL, NULL),
|
||||
|
||||
-- o1-preview-2024-09-12
|
||||
('cm10ivo130000n8x7qopcjjcg', NULL, 'o1-preview-2024-09-12', '(?i)^(o1-preview-2024-09-12)$', NULL, 0.000015, 0.00006, NULL, 'TOKENS', NULL, NULL),
|
||||
|
||||
-- o1-mini
|
||||
('cm10ivwo40000r1x7gg3syjq0', NULL, 'o1-mini', '(?i)^(o1-mini)$', NULL, 0.000003, 0.000012, NULL, 'TOKENS', NULL, NULL),
|
||||
|
||||
-- o1-mini-2024-09-12
|
||||
('cm10iw6p20000wgx7it1hlb22', NULL, 'o1-mini-2024-09-12', '(?i)^(o1-mini-2024-09-12)$', NULL, 0.000003, 0.000012, NULL, 'TOKENS', NULL, NULL)
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Account" ADD COLUMN "refresh_token_expires_in" INTEGER;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "traces_project_id_idx"; -- covered by traces_project_id_timestamp_idx
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "datasets_project_id_idx"; -- covered by datasets_project_id_name_key (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "eval_templates_project_id_idx"; -- covered by eval_templates_project_id_id_idx
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "job_configurations_project_id_idx"; -- covered by job_configurations_project_id_id_idx
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "job_executions_project_id_idx"; -- covered by job_executions_project_id_id_idx
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "llm_api_keys_project_id_provider_idx"; -- covered by llm_api_keys_project_id_provider_key (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "models_project_id_model_name_idx"; -- covered by models_project_id_model_name_start_date_unit_key (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "models_project_id_model_name_start_date_unit_idx"; -- covered by models_project_id_model_name_start_date_unit_key (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "observations_project_id_idx"; -- covered by observations_project_id_start_time_type_idx
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "observations_trace_id_idx"; -- covered by observations_trace_id_project_id_start_time_idx
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "observations_trace_id_project_id_idx"; -- covered by observations_trace_id_project_id_start_time_idx
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "organization_memberships_org_id_idx"; -- covered by organization_memberships_org_id_user_id_key (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "posthog_integrations_project_id_idx"; -- covered by posthog_integrations_pkey (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "prompts_project_id_idx"; -- covered by prompts_project_id_name_version_key (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "prompts_project_id_name_version_idx"; -- covered by prompts_project_id_name_version_key (unique)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- DropIndex
|
||||
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "scores_project_id_idx"; -- covered by scores_project_id_name_idx
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Account" ADD COLUMN "created_at" INTEGER;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "scores" ADD COLUMN "queue_id" TEXT;
|
||||
@@ -0,0 +1,75 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AnnotationQueueStatus" AS ENUM ('PENDING', 'COMPLETED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AnnotationQueueObjectType" AS ENUM ('TRACE', 'OBSERVATION');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "annotation_queues" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"score_config_ids" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"project_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "annotation_queues_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "annotation_queue_items" (
|
||||
"id" TEXT NOT NULL,
|
||||
"queue_id" TEXT NOT NULL,
|
||||
"object_id" TEXT NOT NULL,
|
||||
"object_type" "AnnotationQueueObjectType" NOT NULL,
|
||||
"status" "AnnotationQueueStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"locked_at" TIMESTAMP(3),
|
||||
"locked_by_user_id" TEXT,
|
||||
"annotator_user_id" TEXT,
|
||||
"completed_at" TIMESTAMP(3),
|
||||
"project_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "annotation_queue_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "annotation_queues_id_project_id_idx" ON "annotation_queues"("id", "project_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "annotation_queues_project_id_created_at_idx" ON "annotation_queues"("project_id", "created_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "annotation_queues_project_id_name_key" ON "annotation_queues"("project_id", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "annotation_queue_items_id_project_id_idx" ON "annotation_queue_items"("id", "project_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "annotation_queue_items_project_id_queue_id_status_idx" ON "annotation_queue_items"("project_id", "queue_id", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "annotation_queue_items_object_id_object_type_project_id_que_idx" ON "annotation_queue_items"("object_id", "object_type", "project_id", "queue_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "annotation_queue_items_annotator_user_id_idx" ON "annotation_queue_items"("annotator_user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "annotation_queue_items_created_at_idx" ON "annotation_queue_items"("created_at");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queues" ADD CONSTRAINT "annotation_queues_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_queue_id_fkey" FOREIGN KEY ("queue_id") REFERENCES "annotation_queues"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_locked_by_user_id_fkey" FOREIGN KEY ("locked_by_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_annotator_user_id_fkey" FOREIGN KEY ("annotator_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queue_items" ADD CONSTRAINT "annotation_queue_items_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "llm_api_keys" ADD COLUMN "config" JSONB;
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
-- Temporarily drop observations_view as prompts.config is a calculated column in the view and we need to update it to a JSON type
|
||||
DROP VIEW IF EXISTS "observations_view"; -- Drop view as column was added in 20240705154048_observation_view_add_created_at_updated_at and update view must have same columns
|
||||
|
||||
-- Update prompts.config to JSON type. Use JSON as JSONB is reordering keys for optimization, which is undesired
|
||||
ALTER TABLE prompts ALTER COLUMN config TYPE JSON USING config::JSON;
|
||||
ALTER TABLE prompts ALTER COLUMN config SET DEFAULT '{}'::JSON;
|
||||
|
||||
-- Recreate the view with the updated prompts.config column
|
||||
CREATE VIEW "observations_view" AS -- Specify the columns that should be returned in the view, as calculated columns are added but exist in the observations table already
|
||||
SELECT
|
||||
o.id,
|
||||
o.name,
|
||||
o.start_time,
|
||||
o.end_time,
|
||||
o.parent_observation_id,
|
||||
o.type,
|
||||
o.trace_id,
|
||||
o.metadata,
|
||||
o.model,
|
||||
o."modelParameters",
|
||||
o.input,
|
||||
o.output,
|
||||
o.level,
|
||||
o.status_message,
|
||||
o.completion_start_time,
|
||||
o.completion_tokens,
|
||||
o.prompt_tokens,
|
||||
o.total_tokens,
|
||||
o.version,
|
||||
o.project_id,
|
||||
o.created_at,
|
||||
o.updated_at,
|
||||
o.unit,
|
||||
o.prompt_id,
|
||||
p.name as prompt_name, -- added in this change
|
||||
p.version as prompt_version, -- added in this change
|
||||
o.input_cost,
|
||||
o.output_cost,
|
||||
o.total_cost,
|
||||
o.internal_model,
|
||||
m.id AS "model_id",
|
||||
m.start_date AS "model_start_date",
|
||||
m.input_price,
|
||||
m.output_price,
|
||||
m.total_price,
|
||||
m.tokenizer_config AS "tokenizer_config",
|
||||
CASE
|
||||
WHEN o.calculated_input_cost IS NULL AND o.input_cost IS NULL AND o.output_cost IS NULL AND o.total_cost IS NULL THEN
|
||||
o.prompt_tokens::decimal * m.input_price
|
||||
ELSE
|
||||
COALESCE(o.calculated_input_cost, o.input_cost)
|
||||
END AS "calculated_input_cost",
|
||||
CASE
|
||||
WHEN o.calculated_output_cost IS NULL AND o.input_cost IS NULL AND o.output_cost IS NULL AND o.total_cost IS NULL THEN
|
||||
o.completion_tokens::decimal * m.output_price
|
||||
ELSE
|
||||
COALESCE(o.calculated_output_cost, o.output_cost)
|
||||
END AS "calculated_output_cost",
|
||||
CASE
|
||||
WHEN o.calculated_total_cost IS NULL AND o.input_cost IS NULL AND o.output_cost IS NULL AND o.total_cost IS NULL THEN
|
||||
CASE
|
||||
WHEN m.total_price IS NOT NULL AND o.total_tokens IS NOT NULL THEN
|
||||
m.total_price * o.total_tokens
|
||||
ELSE
|
||||
o.prompt_tokens::decimal * m.input_price +
|
||||
o.completion_tokens::decimal * m.output_price
|
||||
END
|
||||
ELSE
|
||||
COALESCE(o.calculated_total_cost, o.total_cost)
|
||||
END AS "calculated_total_cost",
|
||||
CASE WHEN o.end_time IS NULL THEN NULL ELSE (EXTRACT(EPOCH FROM o."end_time") - EXTRACT(EPOCH FROM o."start_time"))::double precision END AS "latency",
|
||||
CASE WHEN o.completion_start_time IS NOT NULL AND o.start_time IS NOT NULL THEN EXTRACT(EPOCH FROM (completion_start_time - start_time))::double precision ELSE NULL END as "time_to_first_token"
|
||||
|
||||
FROM
|
||||
observations o
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
models.*
|
||||
FROM
|
||||
models
|
||||
WHERE (models.project_id = o.project_id OR models.project_id IS NULL)
|
||||
AND models.model_name = o.internal_model
|
||||
AND (models.start_date < o.start_time OR models.start_date IS NULL)
|
||||
AND o.unit::TEXT = models.unit
|
||||
ORDER BY
|
||||
models.project_id ASC, -- in postgres, NULLs are sorted last when ordering ASC
|
||||
models.start_date DESC NULLS LAST -- now, NULLs are sorted last when ordering DESC as well
|
||||
LIMIT 1
|
||||
) m ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
prompts.*
|
||||
FROM
|
||||
prompts
|
||||
WHERE prompts.id = o.prompt_id
|
||||
AND prompts.project_id = o.project_id
|
||||
LIMIT 1
|
||||
) p ON TRUE
|
||||
|
||||
|
||||
-- requirements:
|
||||
-- 1. The view should return all columns from the observations table
|
||||
-- 2. The view should match with only one model for each observation if:
|
||||
-- a. The model has the same project_id as the observation, otherwise the model without project_id.
|
||||
-- b. The model has the same model_name as the observation
|
||||
-- c. The model has a start_date that is less than the observation start_time, otherwise the model without start_date
|
||||
-- d. The model has the same unit as the observation
|
||||
@@ -0,0 +1,18 @@
|
||||
INSERT INTO models (
|
||||
id,
|
||||
project_id,
|
||||
model_name,
|
||||
match_pattern,
|
||||
start_date,
|
||||
input_price,
|
||||
output_price,
|
||||
total_price,
|
||||
unit,
|
||||
tokenizer_id,
|
||||
tokenizer_config
|
||||
)
|
||||
VALUES
|
||||
-- https://docs.anthropic.com/en/docs/about-claude/models#model-comparison-table
|
||||
('cm2krz1uf000208jjg5653iud', NULL, 'claude-3.5-haiku-20241022', '(?i)^(claude-3-5-sonnet-20241022|anthropic\.claude-3-5-sonnet-20241022-v2:0|claude-3-5-sonnet-V2@20241022)$', NULL, 0.000003, 0.000015, NULL, 'TOKENS', 'claude', NULL),
|
||||
('cm2ks2vzn000308jjh4ze1w7q', NULL, 'claude-3.5-haiku-latest', '(?i)^(claude-3-5-sonnet-latest)$', NULL, 0.000003, 0.000015, NULL, 'TOKENS', 'claude', NULL)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- https://docs.anthropic.com/en/docs/about-claude/models#model-comparison-table
|
||||
UPDATE models SET model_name = 'claude-3.5-sonnet-20241022' WHERE id = 'cm2krz1uf000208jjg5653iud';
|
||||
UPDATE models SET model_name = 'claude-3.5-sonnet-latest' WHERE id = 'cm2ks2vzn000308jjh4ze1w7q';
|
||||
@@ -0,0 +1,62 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "prices" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"model_id" TEXT NOT NULL,
|
||||
"usage_type" TEXT NOT NULL,
|
||||
"price" DECIMAL(65,30) NOT NULL,
|
||||
|
||||
CONSTRAINT "prices_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "prices_model_id_idx" ON "prices"("model_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "prices_model_id_usage_type_key" ON "prices"("model_id", "usage_type");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "prices" ADD CONSTRAINT "prices_model_id_fkey" FOREIGN KEY ("model_id") REFERENCES "models"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Alter Table to make unit column nullable
|
||||
ALTER TABLE "models" ALTER COLUMN "unit" DROP NOT NULL;
|
||||
|
||||
|
||||
-- Create a temporary table to store the new prices for user-defined models
|
||||
CREATE TEMPORARY TABLE temp_prices AS
|
||||
SELECT
|
||||
id AS model_id,
|
||||
'input' AS usage_type,
|
||||
input_price AS price
|
||||
FROM models
|
||||
WHERE project_id IS NOT NULL AND input_price IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT
|
||||
id AS model_id,
|
||||
'output' AS usage_type,
|
||||
output_price AS price
|
||||
FROM models
|
||||
WHERE project_id IS NOT NULL AND output_price IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT
|
||||
id AS model_id,
|
||||
'total' AS usage_type,
|
||||
total_price AS price
|
||||
FROM models
|
||||
WHERE project_id IS NOT NULL AND total_price IS NOT NULL;
|
||||
|
||||
-- Insert into prices table
|
||||
INSERT INTO prices (id, created_at, updated_at, model_id, usage_type, price)
|
||||
SELECT
|
||||
md5(random()::text || clock_timestamp()::text || model_id::text || usage_type::text)::uuid AS id,
|
||||
NOW() AS created_at,
|
||||
NOW() AS updated_at,
|
||||
model_id,
|
||||
usage_type,
|
||||
price
|
||||
FROM temp_prices
|
||||
ON CONFLICT (model_id, usage_type) DO NOTHING;
|
||||
|
||||
-- Drop the temporary table
|
||||
DROP TABLE temp_prices;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "background_migrations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"script" TEXT NOT NULL,
|
||||
"args" JSONB NOT NULL,
|
||||
|
||||
"finished_at" TIMESTAMP(3),
|
||||
"failed_at" TIMESTAMP(3),
|
||||
"failed_reason" TEXT,
|
||||
"worker_id" TEXT,
|
||||
"locked_at" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "background_migrations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "background_migrations_name_key" ON "background_migrations"("name");
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('32859a35-98f5-4a4a-b438-ebc579349e00', '20241024_1216_add_generations_cost_backfill', 'addGenerationsCostBackfill', '{}');
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('5960f22a-748f-480c-b2f3-bc4f9d5d84bc', '20241024_1730_migrate_traces_from_pg_to_ch', 'migrateTracesFromPostgresToClickhouse', '{}');
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('7526e7c9-0026-4595-af2c-369dfd9176ec', '20241024_1737_migrate_observations_from_pg_to_ch', 'migrateObservationsFromPostgresToClickhouse', '{}');
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('94e50334-50d3-4e49-ad2e-9f6d92c85ef7', '20241024_1738_migrate_scores_from_pg_to_ch', 'migrateScoresFromPostgresToClickhouse', '{}');
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- DropIndex
|
||||
DROP INDEX CONCURRENTLY "prices_model_id_idx";
|
||||
File diff suppressed because it is too large
Load Diff
+139
-24
@@ -5,6 +5,7 @@ import {
|
||||
ObservationType,
|
||||
ScoreSource,
|
||||
ScoreDataType,
|
||||
AnnotationQueueObjectType,
|
||||
} from "../src/index";
|
||||
import { hash } from "bcryptjs";
|
||||
import { parseArgs } from "node:util";
|
||||
@@ -12,7 +13,7 @@ import { parseArgs } from "node:util";
|
||||
import { chunk } from "lodash";
|
||||
import { v4 } from "uuid";
|
||||
import { ModelUsageUnit } from "../src";
|
||||
import { getDisplaySecretKey, hashSecretKey } from "../src/server";
|
||||
import { getDisplaySecretKey, hashSecretKey, logger } from "../src/server";
|
||||
import { encrypt } from "../src/encryption";
|
||||
import { redis } from "../src/server/redis/redis";
|
||||
|
||||
@@ -77,6 +78,9 @@ async function main() {
|
||||
create: {
|
||||
id: seedOrgId,
|
||||
name: "Seed Org",
|
||||
cloudConfig: {
|
||||
plan: "Team",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -250,6 +254,11 @@ async function main() {
|
||||
project2,
|
||||
]);
|
||||
|
||||
const queueIds = await generateQueuesForProject(
|
||||
[project1, project2],
|
||||
configIdsAndNames
|
||||
);
|
||||
|
||||
const promptIds = await generatePromptsForProject([project1, project2]);
|
||||
|
||||
const envTags = [null, "development", "staging", "production"];
|
||||
@@ -257,18 +266,26 @@ async function main() {
|
||||
|
||||
const traceVolume = environment === "load" ? LOAD_TRACE_VOLUME : 100;
|
||||
|
||||
const { traces, observations, scores, sessions, events, comments } =
|
||||
createObjects(
|
||||
traceVolume,
|
||||
envTags,
|
||||
colorTags,
|
||||
project1,
|
||||
project2,
|
||||
promptIds,
|
||||
configIdsAndNames
|
||||
);
|
||||
const {
|
||||
traces,
|
||||
observations,
|
||||
scores,
|
||||
sessions,
|
||||
events,
|
||||
comments,
|
||||
queueItems,
|
||||
} = createObjects(
|
||||
traceVolume,
|
||||
envTags,
|
||||
colorTags,
|
||||
project1,
|
||||
project2,
|
||||
promptIds,
|
||||
queueIds,
|
||||
configIdsAndNames
|
||||
);
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
`Seeding ${traces.length} traces, ${observations.length} observations, and ${scores.length} scores`
|
||||
);
|
||||
|
||||
@@ -278,7 +295,8 @@ async function main() {
|
||||
scores,
|
||||
sessions,
|
||||
events,
|
||||
comments
|
||||
comments,
|
||||
queueItems
|
||||
);
|
||||
|
||||
// If openai key is in environment, add it to the projects LLM API keys
|
||||
@@ -295,7 +313,7 @@ async function main() {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
logger.warn(
|
||||
"No OPENAI_API_KEY found in environment. Skipping seeding LLM API key."
|
||||
);
|
||||
}
|
||||
@@ -457,13 +475,13 @@ main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
redis?.disconnect();
|
||||
console.log("Disconnected from postgres and redis");
|
||||
logger.info("Disconnected from postgres and redis");
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
logger.error(e);
|
||||
await prisma.$disconnect();
|
||||
redis?.disconnect();
|
||||
console.log("Disconnected from postgres and redis");
|
||||
logger.info("Disconnected from postgres and redis");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -473,7 +491,8 @@ async function uploadObjects(
|
||||
scores: Prisma.ScoreCreateManyInput[],
|
||||
sessions: Prisma.TraceSessionCreateManyInput[],
|
||||
events: Prisma.ObservationCreateManyInput[],
|
||||
comments: Prisma.CommentCreateManyInput[]
|
||||
comments: Prisma.CommentCreateManyInput[],
|
||||
queueItems: Prisma.AnnotationQueueItemCreateManyInput[]
|
||||
) {
|
||||
let promises: Prisma.PrismaPromise<unknown>[] = [];
|
||||
|
||||
@@ -493,7 +512,7 @@ async function uploadObjects(
|
||||
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
|
||||
console.log(
|
||||
logger.info(
|
||||
`Seeding of Sessions ${((i + 1) / promises.length) * 100}% complete`
|
||||
);
|
||||
await promises[i];
|
||||
@@ -510,7 +529,7 @@ async function uploadObjects(
|
||||
});
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
|
||||
console.log(
|
||||
logger.info(
|
||||
`Seeding of Traces ${((i + 1) / promises.length) * 100}% complete`
|
||||
);
|
||||
await promises[i];
|
||||
@@ -527,7 +546,7 @@ async function uploadObjects(
|
||||
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
|
||||
console.log(
|
||||
logger.info(
|
||||
`Seeding of Observations ${((i + 1) / promises.length) * 100}% complete`
|
||||
);
|
||||
await promises[i];
|
||||
@@ -544,7 +563,7 @@ async function uploadObjects(
|
||||
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
|
||||
console.log(
|
||||
logger.info(
|
||||
`Seeding of Events ${((i + 1) / promises.length) * 100}% complete`
|
||||
);
|
||||
await promises[i];
|
||||
@@ -560,7 +579,7 @@ async function uploadObjects(
|
||||
});
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
|
||||
console.log(
|
||||
logger.info(
|
||||
`Seeding of Scores ${((i + 1) / promises.length) * 100}% complete`
|
||||
);
|
||||
await promises[i];
|
||||
@@ -576,11 +595,27 @@ async function uploadObjects(
|
||||
});
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
|
||||
console.log(
|
||||
logger.info(
|
||||
`Seeding of Comments ${((i + 1) / promises.length) * 100}% complete`
|
||||
);
|
||||
await promises[i];
|
||||
}
|
||||
|
||||
promises = [];
|
||||
chunk(queueItems, chunkSize).forEach((chunk) => {
|
||||
promises.push(
|
||||
prisma.annotationQueueItem.createMany({
|
||||
data: chunk,
|
||||
})
|
||||
);
|
||||
});
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
if (i + 1 >= promises.length || i % Math.ceil(promises.length / 10) === 0)
|
||||
logger.info(
|
||||
`Seeding of Annotation Queue Items ${((i + 1) / promises.length) * 100}% complete`
|
||||
);
|
||||
await promises[i];
|
||||
}
|
||||
}
|
||||
|
||||
function createObjects(
|
||||
@@ -590,6 +625,7 @@ function createObjects(
|
||||
project1: Project,
|
||||
project2: Project,
|
||||
promptIds: Map<string, string[]>,
|
||||
queueIds: Map<string, string[]>,
|
||||
configParams: Map<
|
||||
string,
|
||||
{
|
||||
@@ -607,6 +643,7 @@ function createObjects(
|
||||
const events: Prisma.ObservationCreateManyInput[] = [];
|
||||
const configs: Prisma.ScoreConfigCreateManyInput[] = [];
|
||||
const comments: Prisma.CommentCreateManyInput[] = [];
|
||||
const queueItems: Prisma.AnnotationQueueItemCreateManyInput[] = [];
|
||||
|
||||
for (let i = 0; i < traceVolume; i++) {
|
||||
// print progress to console with a progress bar that refreshes every 10 iterations
|
||||
@@ -689,6 +726,21 @@ function createObjects(
|
||||
}),
|
||||
};
|
||||
|
||||
const queueItem = [
|
||||
...(Math.random() > 0.9 && queueIds.get(projectId)?.[0]
|
||||
? [
|
||||
{
|
||||
queueId: queueIds.get(projectId)?.[0] as string,
|
||||
objectId: trace.id,
|
||||
objectType: AnnotationQueueObjectType.TRACE,
|
||||
projectId,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
queueItems.push(...queueItem);
|
||||
|
||||
const traceScores = [
|
||||
...(Math.random() > 0.5
|
||||
? [
|
||||
@@ -941,6 +993,7 @@ function createObjects(
|
||||
observations,
|
||||
scores,
|
||||
configs,
|
||||
queueItems,
|
||||
sessions: uniqueSessions,
|
||||
events,
|
||||
comments,
|
||||
@@ -1218,6 +1271,7 @@ async function generateConfigs(project: Project) {
|
||||
|
||||
return configNameAndId;
|
||||
}
|
||||
|
||||
function getGenerationInputOutput(): {
|
||||
input: Prisma.InputJsonValue;
|
||||
output: Prisma.InputJsonValue;
|
||||
@@ -1278,3 +1332,64 @@ function getGenerationInputOutput(): {
|
||||
|
||||
return { input, output };
|
||||
}
|
||||
|
||||
async function generateQueuesForProject(
|
||||
projects: Project[],
|
||||
configIdsAndNames: Map<
|
||||
string,
|
||||
{
|
||||
name: string;
|
||||
id: string;
|
||||
dataType: ScoreDataType;
|
||||
categories: ConfigCategory[] | null;
|
||||
}[]
|
||||
>
|
||||
) {
|
||||
const projectIdsToQueues: Map<string, string[]> = new Map();
|
||||
|
||||
await Promise.all(
|
||||
projects.map(async (project) => {
|
||||
const queueIds = await generateQueues(
|
||||
project,
|
||||
configIdsAndNames.get(project.id) ?? []
|
||||
);
|
||||
projectIdsToQueues.set(project.id, queueIds);
|
||||
})
|
||||
);
|
||||
return projectIdsToQueues;
|
||||
}
|
||||
|
||||
async function generateQueues(
|
||||
project: Project,
|
||||
configIdsAndNames: {
|
||||
name: string;
|
||||
id: string;
|
||||
dataType: ScoreDataType;
|
||||
categories: ConfigCategory[] | null;
|
||||
}[]
|
||||
) {
|
||||
const queue = {
|
||||
id: `queue-${v4()}`,
|
||||
name: "Default",
|
||||
description: "Default queue",
|
||||
scoreConfigIds: configIdsAndNames.map((config) => config.id),
|
||||
projectId: project.id,
|
||||
};
|
||||
|
||||
await prisma.annotationQueue.upsert({
|
||||
where: {
|
||||
projectId_name: {
|
||||
projectId: queue.projectId,
|
||||
name: queue.name,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
...queue,
|
||||
},
|
||||
update: {
|
||||
id: queue.id,
|
||||
},
|
||||
});
|
||||
|
||||
return [queue.id];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import { prisma } from "../src/db";
|
||||
import {
|
||||
clickhouseClient,
|
||||
getDisplaySecretKey,
|
||||
hashSecretKey,
|
||||
logger,
|
||||
} from "../src/server";
|
||||
import { prepareClickhouse } from "./prepareClickhouse";
|
||||
import { redis } from "../src/server";
|
||||
|
||||
const createRandomProjectId = () => randomUUID().toString();
|
||||
|
||||
const prepareProjectsAndApiKeys = async (
|
||||
numOfProjects: number,
|
||||
opts: { requiredProjectIds: string[] }
|
||||
) => {
|
||||
const { requiredProjectIds } = opts;
|
||||
const projectsToCreate = numOfProjects - requiredProjectIds.length;
|
||||
const projectIds = [...requiredProjectIds];
|
||||
|
||||
for (let i = 0; i < projectsToCreate; i++) {
|
||||
projectIds.push(createRandomProjectId());
|
||||
}
|
||||
|
||||
const operations = projectIds.map(async (projectId) => {
|
||||
const orgId = `org-${projectId}`;
|
||||
|
||||
await prisma.organization.upsert({
|
||||
where: { id: orgId },
|
||||
update: {},
|
||||
create: {
|
||||
id: orgId,
|
||||
name: `Organization for ${projectId}`,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.project.upsert({
|
||||
where: { id: projectId },
|
||||
update: {},
|
||||
create: {
|
||||
id: projectId,
|
||||
name: `Project ${projectId}`,
|
||||
orgId: orgId,
|
||||
},
|
||||
});
|
||||
|
||||
const apiKeyId = `api-key-${projectId}`;
|
||||
const apiKeyExists = await prisma.apiKey.findUnique({
|
||||
where: { id: apiKeyId },
|
||||
});
|
||||
if (!apiKeyExists) {
|
||||
const sk = await hashSecretKey(
|
||||
`sk-${Math.random().toString(36).substr(2, 9)}`
|
||||
);
|
||||
await prisma.apiKey.create({
|
||||
data: {
|
||||
id: apiKeyId,
|
||||
note: `API Key for ${projectId}`,
|
||||
publicKey: `pk-${Math.random().toString(36).substr(2, 9)}`,
|
||||
hashedSecretKey: sk,
|
||||
displaySecretKey: getDisplaySecretKey(sk),
|
||||
project: {
|
||||
connect: {
|
||||
id: projectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(operations);
|
||||
return projectIds;
|
||||
};
|
||||
|
||||
async function main() {
|
||||
let numOfProjects = parseInt(process.argv[2], 10);
|
||||
let numberOfDays = parseInt(process.argv[3], 10);
|
||||
let totalObservations = parseInt(process.argv[4], 10);
|
||||
|
||||
if (isNaN(totalObservations)) {
|
||||
logger.warn(
|
||||
"Total observations not provided or invalid. Defaulting to 1000 observations."
|
||||
);
|
||||
totalObservations = 1000;
|
||||
}
|
||||
|
||||
if (isNaN(numOfProjects)) {
|
||||
logger.warn(
|
||||
"Number of projects not provided or invalid. Defaulting to 10 projects."
|
||||
);
|
||||
numOfProjects = 10;
|
||||
}
|
||||
|
||||
if (isNaN(numberOfDays)) {
|
||||
logger.warn(
|
||||
"Number of days not provided or invalid. Defaulting to 3 days."
|
||||
);
|
||||
numberOfDays = 3;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Preparing Clickhouse for ${numOfProjects} projects and ${numberOfDays} days with max Observations ${totalObservations}.`
|
||||
);
|
||||
|
||||
try {
|
||||
const projectIds = [
|
||||
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
|
||||
"239ad00f-562f-411d-af14-831c75ddd875",
|
||||
];
|
||||
|
||||
const createdProjectIds = await prepareProjectsAndApiKeys(numOfProjects, {
|
||||
requiredProjectIds: projectIds,
|
||||
});
|
||||
|
||||
await prepareClickhouse(createdProjectIds, {
|
||||
numberOfDays,
|
||||
totalObservations: totalObservations ?? 1000,
|
||||
});
|
||||
|
||||
logger.info("Clickhouse preparation completed successfully.");
|
||||
} catch (error) {
|
||||
logger.error("Error during Clickhouse preparation:", error);
|
||||
} finally {
|
||||
await clickhouseClient.close();
|
||||
await prisma.$disconnect();
|
||||
redis?.disconnect();
|
||||
logger.info("Disconnected from Clickhouse.");
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,218 @@
|
||||
import { clickhouseClient, logger } from "../src/server";
|
||||
|
||||
function randn_bm(min: number, max: number, skew: number) {
|
||||
let u = 0,
|
||||
v = 0;
|
||||
while (u === 0) u = Math.random(); //Converting [0,1) to (0,1)
|
||||
while (v === 0) v = Math.random();
|
||||
let num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
||||
|
||||
num = num / 10.0 + 0.5; // Translate to 0 -> 1
|
||||
if (num > 1 || num < 0)
|
||||
num = randn_bm(min, max, skew); // resample between 0 and 1 if out of range
|
||||
else {
|
||||
num = Math.pow(num, skew); // Skew
|
||||
num *= max - min; // Stretch to fill range
|
||||
num += min; // offset to min
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export const prepareClickhouse = async (
|
||||
projectIds: string[],
|
||||
opts: {
|
||||
numberOfDays: number;
|
||||
totalObservations: number;
|
||||
},
|
||||
) => {
|
||||
logger.info(
|
||||
`Preparing Clickhouse for ${projectIds.length} projects and ${opts.numberOfDays} days.`,
|
||||
);
|
||||
|
||||
const projectData = projectIds.map((projectId) => {
|
||||
const observationsPerProject = Math.ceil(
|
||||
randn_bm(0, opts.totalObservations, 2),
|
||||
); // Skew the number of observations
|
||||
|
||||
const tracesPerProject = Math.floor(observationsPerProject / 6); // On average, one trace should have 6 observations
|
||||
const scoresPerProject = tracesPerProject * 10; // On average, one trace should have 10 scores
|
||||
return {
|
||||
projectId,
|
||||
observationsPerProject,
|
||||
tracesPerProject,
|
||||
scoresPerProject,
|
||||
};
|
||||
});
|
||||
|
||||
for (const data of projectData) {
|
||||
const {
|
||||
projectId,
|
||||
tracesPerProject,
|
||||
observationsPerProject,
|
||||
scoresPerProject,
|
||||
} = data;
|
||||
logger.info(
|
||||
`Preparing Clickhouse for ${projectId}: Traces: ${tracesPerProject}, Scores: ${scoresPerProject}, Observations: ${observationsPerProject}`,
|
||||
);
|
||||
|
||||
const tracesQuery = `
|
||||
INSERT INTO traces
|
||||
SELECT toString(number) AS id,
|
||||
toDateTime(now() - randUniform(0, ${opts.numberOfDays} * 24 * 60 * 60)) AS timestamp,
|
||||
concat('name_', toString(rand() % 100)) AS name,
|
||||
concat('user_id_', toString(randUniform(0, 100))) AS user_id,
|
||||
map('key', 'value') AS metadata,
|
||||
concat('release_', toString(randUniform(0, 100))) AS release,
|
||||
concat('version_', toString(randUniform(0, 100))) AS version,
|
||||
'${projectId}' AS project_id,
|
||||
if(rand() < 0.8, true, false) as public,
|
||||
if(rand() < 0.8, true, false) as bookmarked,
|
||||
array('tag1', 'tag2') as tags,
|
||||
repeat('input', toInt64(randExponential(1 / 100))) AS input,
|
||||
repeat('output', toInt64(randExponential(1 / 100))) AS output,
|
||||
concat('session_', toString(rand() % 100)) AS session_id,
|
||||
timestamp AS created_at,
|
||||
timestamp AS updated_at,
|
||||
timestamp AS event_ts,
|
||||
0 AS is_deleted
|
||||
FROM numbers(${tracesPerProject});
|
||||
`;
|
||||
|
||||
const observationsQuery = `
|
||||
INSERT INTO observations
|
||||
SELECT toString(number) AS id,
|
||||
toString(floor(randUniform(0, ${tracesPerProject}))) AS trace_id,
|
||||
'${projectId}' AS project_id,
|
||||
if(rand() < 0.47, 'GENERATION', if(rand() < 0.94, 'SPAN', 'EVENT')) AS type,
|
||||
toString(rand()) AS parent_observation_id,
|
||||
toDateTime(now() - randUniform(0, ${opts.numberOfDays} * 24 * 60 * 60)) AS start_time,
|
||||
addSeconds(start_time, if(rand() < 0.6, floor(randUniform(0, 20)), floor(randUniform(0, 3600)))) AS end_time,
|
||||
concat('name', toString(rand() % 100)) AS name,
|
||||
map('key', 'value') AS metadata,
|
||||
if(rand() < 0.9, 'DEFAULT', if(rand() < 0.5, 'ERROR', if(rand() < 0.5, 'DEBUG', 'WARNING'))) AS level,
|
||||
'status_message' AS status_message,
|
||||
'version' AS version,
|
||||
repeat('input', toInt64(randExponential(1 / 100))) AS input,
|
||||
repeat('output', toInt64(randExponential(1 / 100))) AS output,
|
||||
case
|
||||
when number % 2 = 0 then 'claude-3-haiku-20240307'
|
||||
else 'gpt-4'
|
||||
end as provided_model_name,
|
||||
case
|
||||
when number % 2 = 0 then 'cltr0w45b000008k1407o9qv1'
|
||||
else 'clrntkjgy000f08jx79v9g1xj'
|
||||
end as internal_model_id,
|
||||
'{"temperature": 0.7, "max_tokens": 15d0}' AS model_parameters,
|
||||
map('input', toUInt64(randUniform(0, 1000)), 'output', toUInt64(randUniform(0, 1000)), 'total', toUInt64(randUniform(0, 2000))) AS provided_usage_details,
|
||||
map('input', toUInt64(randUniform(0, 1000)), 'output', toUInt64(randUniform(0, 1000)), 'total', toUInt64(randUniform(0, 2000))) AS usage_details,
|
||||
map('input', toDecimal64(randUniform(0, 1000), 12), 'output', toDecimal64(randUniform(0, 1000), 12), 'total', toDecimal64(randUniform(0, 2000), 12)) AS provided_cost_details,
|
||||
map('input', toDecimal64(randUniform(0, 1000), 12), 'output', toDecimal64(randUniform(0, 1000), 12), 'total', toDecimal64(randUniform(0, 2000), 12)) AS cost_details,
|
||||
toDecimal64(randUniform(0, 2000), 12) AS total_cost,
|
||||
start_time AS completion_start_time,
|
||||
toString(rand()) AS prompt_id,
|
||||
toString(rand()) AS prompt_name,
|
||||
1000 AS prompt_version,
|
||||
start_time AS created_at,
|
||||
start_time AS updated_at,
|
||||
start_time AS event_ts,
|
||||
0 AS is_deleted
|
||||
FROM numbers(${observationsPerProject});
|
||||
`;
|
||||
|
||||
const scoresQuery = `
|
||||
INSERT INTO scores
|
||||
SELECT toString(floor(randUniform(0, 100))) AS id,
|
||||
toDateTime(now() - randUniform(0, ${opts.numberOfDays} * 24 * 60 * 60)) AS timestamp,
|
||||
'${projectId}' AS project_id,
|
||||
toString(floor(randUniform(0, ${tracesPerProject}))) AS trace_id,
|
||||
if(
|
||||
rand() > 0.9,
|
||||
toString(floor(randUniform(0, ${observationsPerProject}))),
|
||||
NULL
|
||||
) AS observation_id,
|
||||
concat('name_', toString(rand() % 100)) AS name,
|
||||
randUniform(0, 100) as value,
|
||||
'API' as source,
|
||||
'comment' as comment,
|
||||
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,
|
||||
toString(rand() % 100) as string_value,
|
||||
NULL as queue_id,
|
||||
timestamp AS created_at,
|
||||
timestamp AS updated_at,
|
||||
timestamp AS event_ts,
|
||||
0 AS is_deleted
|
||||
FROM numbers(${scoresPerProject});
|
||||
`;
|
||||
|
||||
const queries = [tracesQuery, scoresQuery, observationsQuery];
|
||||
|
||||
for (const query of queries) {
|
||||
logger.info(`Executing query: ${query}`);
|
||||
await clickhouseClient.command({
|
||||
query,
|
||||
clickhouse_settings: {
|
||||
wait_end_of_query: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const tables = ["traces", "scores", "observations"];
|
||||
for (const table of tables) {
|
||||
const query = `
|
||||
SELECT
|
||||
project_id,
|
||||
count() AS per_project_count,
|
||||
bar(per_project_count, 0, (
|
||||
SELECT count(*)
|
||||
FROM ${table}
|
||||
), 50) AS bar_representation
|
||||
FROM ${table}
|
||||
GROUP BY project_id
|
||||
ORDER BY count() desc
|
||||
`;
|
||||
|
||||
const result = await clickhouseClient.query({
|
||||
query,
|
||||
format: "TabSeparated",
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`${table.charAt(0).toUpperCase() + table.slice(1)} per Project: \n` +
|
||||
(await result.text()),
|
||||
);
|
||||
}
|
||||
|
||||
const tablesWithDateColumns = [
|
||||
{ name: "traces", dateColumn: "timestamp" },
|
||||
{ name: "scores", dateColumn: "timestamp" },
|
||||
{ name: "observations", dateColumn: "start_time" },
|
||||
];
|
||||
|
||||
for (const { name: table, dateColumn } of tablesWithDateColumns) {
|
||||
const query = `
|
||||
SELECT
|
||||
toDate(${dateColumn}) AS event_date,
|
||||
count() AS per_date_count,
|
||||
bar(per_date_count, 0, (
|
||||
SELECT count(*)
|
||||
FROM ${table}
|
||||
), 50) AS bar_representation
|
||||
FROM ${table}
|
||||
GROUP BY event_date
|
||||
ORDER BY event_date desc
|
||||
`;
|
||||
|
||||
const result = await clickhouseClient.query({
|
||||
query,
|
||||
format: "TabSeparated",
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`${table.charAt(0).toUpperCase() + table.slice(1)} per Date: \n` +
|
||||
(await result.text()),
|
||||
);
|
||||
}
|
||||
};
|
||||
+80
-20
@@ -1,7 +1,7 @@
|
||||
// This file exports the prisma db connection, the Prisma Object, and the Typescript types.
|
||||
// This is not imported in the index.ts file of this package, as we must not import this into FE code.
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
import { env } from "process";
|
||||
import kyselyExtension from "prisma-extension-kysely";
|
||||
import {
|
||||
@@ -11,21 +11,86 @@ import {
|
||||
PostgresQueryCompiler,
|
||||
} from "kysely";
|
||||
import { DB } from ".";
|
||||
import { logger } from "./server";
|
||||
|
||||
// Instantiated according to the Prisma documentation
|
||||
// https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices
|
||||
export class PrismaClientSingleton {
|
||||
private static instance: PrismaClient;
|
||||
|
||||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient({
|
||||
log:
|
||||
env.NODE_ENV === "development"
|
||||
? ["query", "error", "warn"]
|
||||
: ["error", "warn"],
|
||||
public static getInstance(): PrismaClient {
|
||||
if (PrismaClientSingleton.instance) {
|
||||
return PrismaClientSingleton.instance;
|
||||
}
|
||||
|
||||
PrismaClientSingleton.instance = createPrismaInstance();
|
||||
|
||||
return PrismaClientSingleton.instance;
|
||||
}
|
||||
}
|
||||
|
||||
const createPrismaInstance = () => {
|
||||
const client = new PrismaClient<
|
||||
Prisma.PrismaClientOptions,
|
||||
"warn" | "error" | "query"
|
||||
>({
|
||||
log: [
|
||||
{ emit: "event", level: "query" },
|
||||
{ emit: "event", level: "error" },
|
||||
{ emit: "event", level: "warn" },
|
||||
],
|
||||
});
|
||||
|
||||
if (env.NODE_ENV === "development") {
|
||||
client.$on("query", (event) => {
|
||||
logger.info(`prisma:query ${event.query}, ${event.duration}ms`);
|
||||
});
|
||||
}
|
||||
|
||||
client.$on("warn", (event) => {
|
||||
logger.warn(`prisma:warn ${event.message}`);
|
||||
});
|
||||
|
||||
client.$on("error", (event) => {
|
||||
logger.error(`prisma:error ${event.message}`);
|
||||
});
|
||||
return client;
|
||||
};
|
||||
|
||||
const kyselySingleton = (prismaClient: PrismaClient) => {
|
||||
return prismaClient.$extends(
|
||||
export class KyselySingleton {
|
||||
private static instance: { $kysely: Kysely<DB> };
|
||||
|
||||
public static getInstance() {
|
||||
if (KyselySingleton.instance) {
|
||||
return KyselySingleton.instance;
|
||||
}
|
||||
|
||||
KyselySingleton.instance = PrismaClientSingleton.getInstance().$extends(
|
||||
kyselyExtension({
|
||||
kysely: (driver) =>
|
||||
new Kysely<DB>({
|
||||
dialect: {
|
||||
// This is where the magic happens!
|
||||
createDriver: () => driver,
|
||||
// Don't forget to customize these to match your database!
|
||||
createAdapter: () => new PostgresAdapter(),
|
||||
createIntrospector: (db) => new PostgresIntrospector(db),
|
||||
createQueryCompiler: () => new PostgresQueryCompiler(),
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
return KyselySingleton.instance;
|
||||
}
|
||||
}
|
||||
|
||||
declare const globalThis: {
|
||||
prismaGlobal: PrismaClient | undefined;
|
||||
kyselyPrismaGlobal: { $kysely: Kysely<DB> } | undefined;
|
||||
} & typeof global;
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
globalThis.prismaGlobal ??= createPrismaInstance(); // regular instantiation
|
||||
globalThis.kyselyPrismaGlobal ??= globalThis.prismaGlobal.$extends(
|
||||
kyselyExtension({
|
||||
kysely: (driver) =>
|
||||
new Kysely<DB>({
|
||||
@@ -40,16 +105,11 @@ const kyselySingleton = (prismaClient: PrismaClient) => {
|
||||
}),
|
||||
})
|
||||
);
|
||||
};
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
|
||||
var kyselyPrisma: undefined | ReturnType<typeof kyselySingleton>;
|
||||
}
|
||||
|
||||
export const prisma = globalThis.prisma ?? prismaClientSingleton();
|
||||
export const kyselyPrisma = globalThis.kyselyPrisma ?? kyselySingleton(prisma);
|
||||
export const prisma =
|
||||
globalThis.prismaGlobal ?? PrismaClientSingleton.getInstance();
|
||||
export const kyselyPrisma =
|
||||
globalThis.kyselyPrismaGlobal ?? KyselySingleton.getInstance();
|
||||
|
||||
export * from "@prisma/client";
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalThis.prisma = prisma;
|
||||
|
||||
+11
-11
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { removeEmptyEnvVariables } from "./utils/environment";
|
||||
|
||||
const EnvSchema = z.object({
|
||||
NODE_ENV: z
|
||||
@@ -21,7 +22,7 @@ const EnvSchema = z.object({
|
||||
.string()
|
||||
.length(
|
||||
64,
|
||||
"ENCRYPTION_KEY must be 256 bits, 64 string characters in hex format, generate via: openssl rand -hex 32"
|
||||
"ENCRYPTION_KEY must be 256 bits, 64 string characters in hex format, generate via: openssl rand -hex 32",
|
||||
)
|
||||
.optional(),
|
||||
LANGFUSE_CACHE_PROMPT_ENABLED: z.enum(["true", "false"]).default("false"),
|
||||
@@ -29,15 +30,14 @@ const EnvSchema = z.object({
|
||||
CLICKHOUSE_URL: z.string().url().optional(),
|
||||
CLICKHOUSE_USER: z.string().optional(),
|
||||
CLICKHOUSE_PASSWORD: z.string().optional(),
|
||||
LANGFUSE_INGESTION_FLUSH_DELAY_MS: z.coerce
|
||||
.number()
|
||||
.nonnegative()
|
||||
.default(10000),
|
||||
LANGFUSE_INGESTION_FLUSH_ATTEMPTS: z.coerce.number().positive().default(3),
|
||||
LANGFUSE_INGESTION_BUFFER_TTL_SECONDS: z.coerce
|
||||
.number()
|
||||
.positive()
|
||||
.default(60 * 10),
|
||||
SALT: z.string().optional(), // used by components imported by web package
|
||||
LANGFUSE_LOG_LEVEL: z
|
||||
.enum(["trace", "debug", "info", "warn", "error", "fatal"])
|
||||
.optional(),
|
||||
LANGFUSE_LOG_FORMAT: z.enum(["text", "json"]).default("text"),
|
||||
ENABLE_AWS_CLOUDWATCH_METRIC_PUBLISHING: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
});
|
||||
|
||||
export const env = EnvSchema.parse(process.env);
|
||||
export const env = EnvSchema.parse(removeEmptyEnvVariables(process.env));
|
||||
|
||||
@@ -32,6 +32,7 @@ const CreateAnnotationScoreBase = z.object({
|
||||
configId: z.string().optional(),
|
||||
observationId: z.string().optional(),
|
||||
comment: z.string().nullish(),
|
||||
queueId: z.string().nullish(),
|
||||
});
|
||||
|
||||
const UpdateAnnotationScoreBase = CreateAnnotationScoreBase.extend({
|
||||
@@ -57,3 +58,15 @@ export const UpdateAnnotationScoreData = z.discriminatedUnion("dataType", [
|
||||
UpdateAnnotationScoreBase.merge(CategoricalData),
|
||||
UpdateAnnotationScoreBase.merge(BooleanData),
|
||||
]);
|
||||
|
||||
// annotation queues
|
||||
|
||||
export const CreateQueueData = z.object({
|
||||
name: z.string().min(1).max(35),
|
||||
description: z.string().max(1000).optional(),
|
||||
scoreConfigIds: z.array(z.string()).min(1, {
|
||||
message: "At least 1 score config must be selected",
|
||||
}),
|
||||
});
|
||||
|
||||
export type CreateQueue = z.infer<typeof CreateQueueData>;
|
||||
|
||||
@@ -19,6 +19,8 @@ export enum BatchExportFileFormat {
|
||||
|
||||
export enum BatchExportTableName {
|
||||
Sessions = "sessions",
|
||||
Traces = "traces",
|
||||
Generations = "generations",
|
||||
}
|
||||
|
||||
export const exportOptions: Record<
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import z from "zod";
|
||||
import { LLMAdapter } from "../..";
|
||||
|
||||
export const langfuseObjects = [
|
||||
"trace",
|
||||
@@ -77,18 +76,6 @@ export const availableEvalVariables = [
|
||||
},
|
||||
];
|
||||
|
||||
export const evalModelsByAdapter: Record<LLMAdapter, string[]> = {
|
||||
[LLMAdapter.OpenAI]: [
|
||||
"gpt-4o",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4o-2024-08-06", // cheaper than gpt-4o
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-3.5-turbo",
|
||||
] as const,
|
||||
[LLMAdapter.Anthropic]: [] as const,
|
||||
[LLMAdapter.Azure]: [] as const,
|
||||
};
|
||||
|
||||
export const OutputSchema = z.object({
|
||||
reasoning: z.string(),
|
||||
score: z.string(),
|
||||
|
||||
@@ -55,6 +55,7 @@ const ScoreBase = z.object({
|
||||
configId: z.string().nullish(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
queueId: z.string().nullish(),
|
||||
});
|
||||
|
||||
const BaseScoreBody = z.object({
|
||||
@@ -234,6 +235,8 @@ export const GetScoresQuery = z.object({
|
||||
userId: z.string().nullish(),
|
||||
dataType: z.enum(ScoreDataType).nullish(),
|
||||
configId: z.string().nullish(),
|
||||
queueId: z.string().nullish(),
|
||||
traceTags: z.union([z.array(z.string()), z.string()]).nullish(),
|
||||
name: z.string().nullish(),
|
||||
fromTimestamp: stringDateTime,
|
||||
toTimestamp: stringDateTime,
|
||||
@@ -255,6 +258,7 @@ const LegacyGetScoreResponseDataV1 = z.intersection(
|
||||
z.object({
|
||||
trace: z.object({
|
||||
userId: z.string().nullish(),
|
||||
tags: z.array(z.string()).nullish(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
export * from "./constants";
|
||||
export * from "./queries";
|
||||
export * from "./interfaces/filters";
|
||||
export * from "./interfaces/orderBy";
|
||||
export * from "./interfaces/cloudConfigSchema";
|
||||
export * from "./interfaces/parseDbOrg";
|
||||
export * from "./interfaces/customLLMProviderConfigSchemas";
|
||||
export * from "./tableDefinitions";
|
||||
export * from "./types";
|
||||
export * from "./filterToPrisma";
|
||||
export * from "./orderByToPrisma";
|
||||
export * from "./tracesTable";
|
||||
export * from "./server/auth/auth";
|
||||
export * from "./tableDefinitions/tracesTable";
|
||||
export * from "./server/auth/apiKeys";
|
||||
export * from "./observationsTable";
|
||||
export * from "./utils/zod";
|
||||
export * from "./utils/json";
|
||||
@@ -21,7 +19,6 @@ export { env } from "./env";
|
||||
|
||||
// llm api
|
||||
export * from "./server/llm/types";
|
||||
export * from "./server/llm/fetchLLMCompletion";
|
||||
|
||||
// evals
|
||||
export * from "./features/evals/types";
|
||||
@@ -43,3 +40,5 @@ export { type DB } from "../prisma/generated/types";
|
||||
|
||||
// errors
|
||||
export * from "./errors/index";
|
||||
|
||||
export * from "./utils/environment";
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const BedrockConfigSchema = z.object({ region: z.string() });
|
||||
export type BedrockConfig = z.infer<typeof BedrockConfigSchema>;
|
||||
|
||||
export const BedrockCredentialSchema = z
|
||||
.object({
|
||||
accessKeyId: z.string(),
|
||||
secretAccessKey: z.string(),
|
||||
})
|
||||
.optional();
|
||||
export type BedrockCredential = z.infer<typeof BedrockCredentialSchema>;
|
||||
@@ -6,6 +6,7 @@ export const RateLimitResource = z.enum([
|
||||
"public-api",
|
||||
"public-api-metrics",
|
||||
"prompts",
|
||||
"legacy-ingestion",
|
||||
]);
|
||||
|
||||
// result of the rate limit check.
|
||||
|
||||
@@ -52,7 +52,8 @@ export const observationsTableCols: ColumnDefinition[] = [
|
||||
name: "Time To First Token (s)",
|
||||
id: "timeToFirstToken",
|
||||
type: "number",
|
||||
internal: 'o."completion_start_time" - o."start_time"',
|
||||
internal:
|
||||
'EXTRACT(EPOCH FROM (o."completion_start_time" - o."start_time"))',
|
||||
nullable: true,
|
||||
},
|
||||
{
|
||||
@@ -62,10 +63,10 @@ export const observationsTableCols: ColumnDefinition[] = [
|
||||
internal: '"latency"',
|
||||
},
|
||||
{
|
||||
name: "Time Per Output Token (s)",
|
||||
id: "timePerOutputToken",
|
||||
name: "Tokens per second",
|
||||
id: "tokensPerSecond",
|
||||
type: "number",
|
||||
internal: '"latency" / o."completion_tokens"',
|
||||
internal: 'o."completion_tokens" / "latency"',
|
||||
nullable: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { createSessionsAllQuery } from "./createSessionsAllQuery";
|
||||
@@ -0,0 +1,81 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { compare, hash } from "bcryptjs";
|
||||
import { randomUUID } from "crypto";
|
||||
import * as crypto from "crypto";
|
||||
import { env } from "../../env";
|
||||
|
||||
export function getDisplaySecretKey(secretKey: string) {
|
||||
return secretKey.slice(0, 6) + "..." + secretKey.slice(-4);
|
||||
}
|
||||
|
||||
export async function hashSecretKey(key: string) {
|
||||
// legacy, uses bcrypt, transformed into hashed key upon first use
|
||||
const hashedKey = await hash(key, 11);
|
||||
return hashedKey;
|
||||
}
|
||||
|
||||
async function generateKeySet() {
|
||||
return {
|
||||
pk: `pk-lf-${randomUUID()}`,
|
||||
sk: `sk-lf-${randomUUID()}`,
|
||||
};
|
||||
}
|
||||
|
||||
export async function verifySecretKey(key: string, hashedKey: string) {
|
||||
const isValid = await compare(key, hashedKey);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export function createShaHash(privateKey: string, salt: string): string {
|
||||
const hash = crypto
|
||||
.createHash("sha256")
|
||||
.update(privateKey)
|
||||
.update(crypto.createHash("sha256").update(salt, "utf8").digest("hex"))
|
||||
.digest("hex");
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
export async function createAndAddApiKeysToDb(p: {
|
||||
prisma: PrismaClient;
|
||||
projectId: string;
|
||||
note?: string;
|
||||
predefinedKeys?: {
|
||||
secretKey: string;
|
||||
publicKey: string;
|
||||
};
|
||||
}) {
|
||||
const salt = env.SALT;
|
||||
if (!salt) {
|
||||
throw new Error("SALT is not set");
|
||||
}
|
||||
|
||||
const { pk, sk } = p.predefinedKeys
|
||||
? { pk: p.predefinedKeys.publicKey, sk: p.predefinedKeys.secretKey }
|
||||
: await generateKeySet();
|
||||
|
||||
const hashedSk = await hashSecretKey(sk);
|
||||
const displaySk = getDisplaySecretKey(sk);
|
||||
|
||||
const hashFromProvidedKey = createShaHash(sk, salt);
|
||||
|
||||
const apiKey = await p.prisma.apiKey.create({
|
||||
data: {
|
||||
projectId: p.projectId,
|
||||
publicKey: pk,
|
||||
hashedSecretKey: hashedSk,
|
||||
displaySecretKey: displaySk,
|
||||
fastHashedSecretKey: hashFromProvidedKey,
|
||||
note: p.note,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: apiKey.id,
|
||||
createdAt: apiKey.createdAt,
|
||||
note: apiKey.note,
|
||||
publicKey: apiKey.publicKey,
|
||||
secretKey: sk,
|
||||
displaySecretKey: displaySk,
|
||||
};
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import { compare, hash } from "bcryptjs";
|
||||
import { randomUUID } from "crypto";
|
||||
import * as crypto from "crypto";
|
||||
import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers/oauth";
|
||||
|
||||
export function generateSecretKey() {
|
||||
return `sk-lf-${randomUUID()}`;
|
||||
}
|
||||
|
||||
export function generatePublicKey() {
|
||||
return `pk-lf-${randomUUID()}`;
|
||||
}
|
||||
|
||||
export function getDisplaySecretKey(secretKey: string) {
|
||||
return secretKey.slice(0, 6) + "..." + secretKey.slice(-4);
|
||||
}
|
||||
|
||||
export async function hashSecretKey(key: string) {
|
||||
// legacy, uses bcrypt, transformed into hashed key upon first use
|
||||
const hashedKey = await hash(key, 11);
|
||||
return hashedKey;
|
||||
}
|
||||
|
||||
export async function generateKeySet() {
|
||||
const pk = generatePublicKey();
|
||||
const sk = generateSecretKey();
|
||||
const hashedSk = await hashSecretKey(sk);
|
||||
const displaySk = getDisplaySecretKey(sk);
|
||||
|
||||
return {
|
||||
pk,
|
||||
sk,
|
||||
hashedSk,
|
||||
displaySk,
|
||||
};
|
||||
}
|
||||
|
||||
export async function verifySecretKey(key: string, hashedKey: string) {
|
||||
const isValid = await compare(key, hashedKey);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export function createShaHash(privateKey: string, salt: string): string {
|
||||
const hash = crypto
|
||||
.createHash("sha256")
|
||||
.update(privateKey)
|
||||
.update(crypto.createHash("sha256").update(salt, "utf8").digest("hex"))
|
||||
.digest("hex");
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
export interface CustomSSOUser extends Record<string, any> {
|
||||
email: string;
|
||||
id: string;
|
||||
name: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export function CustomSSOProvider<P extends CustomSSOUser>(
|
||||
options: OAuthUserConfig<P>
|
||||
): OAuthConfig<P> {
|
||||
return {
|
||||
id: "custom",
|
||||
name: "CustomSSOProvider",
|
||||
type: "oauth",
|
||||
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
|
||||
authorization: { params: { scope: "openid email profile" } }, // overridden by options.authorization to be able to set custom scopes, deep merged with this default
|
||||
checks: ["pkce", "state"],
|
||||
idToken: true,
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name,
|
||||
email: profile.email,
|
||||
image: null,
|
||||
};
|
||||
},
|
||||
options,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers/oauth";
|
||||
|
||||
interface CustomSSOUser extends Record<string, any> {
|
||||
email: string;
|
||||
id: string;
|
||||
name: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export function CustomSSOProvider<P extends CustomSSOUser>(
|
||||
options: OAuthUserConfig<P>
|
||||
): OAuthConfig<P> {
|
||||
return {
|
||||
id: "custom",
|
||||
name: "CustomSSOProvider",
|
||||
type: "oauth",
|
||||
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
|
||||
authorization: { params: { scope: "openid email profile" } }, // overridden by options.authorization to be able to set custom scopes, deep merged with this default
|
||||
checks: ["pkce", "state"],
|
||||
idToken: true,
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name,
|
||||
email: profile.email,
|
||||
image: null,
|
||||
};
|
||||
},
|
||||
options,
|
||||
};
|
||||
}
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
import { createClient } from "@clickhouse/client";
|
||||
|
||||
import { env } from "../env";
|
||||
import { env } from "../../env";
|
||||
|
||||
export type ClickhouseClientType = ReturnType<typeof createClient>;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { LangfuseNotFoundError } from "../../errors";
|
||||
import { eventTypes } from "../ingestion/types";
|
||||
import { ClickhouseTableName, ClickhouseTableNames } from "./schema";
|
||||
|
||||
export const isValidTableName = (
|
||||
tableName: string,
|
||||
): tableName is ClickhouseTableName =>
|
||||
Object.keys(ClickhouseTableNames).includes(tableName);
|
||||
|
||||
export type IngestionEntityTypes =
|
||||
| "trace"
|
||||
| "observation"
|
||||
| "score"
|
||||
| "sdk_log";
|
||||
|
||||
export const getClickhouseEntityType = (
|
||||
eventType: string,
|
||||
): IngestionEntityTypes => {
|
||||
switch (eventType) {
|
||||
case eventTypes.TRACE_CREATE:
|
||||
return "trace";
|
||||
case eventTypes.OBSERVATION_CREATE:
|
||||
case eventTypes.OBSERVATION_UPDATE:
|
||||
case eventTypes.EVENT_CREATE:
|
||||
case eventTypes.SPAN_CREATE:
|
||||
case eventTypes.SPAN_UPDATE:
|
||||
case eventTypes.GENERATION_CREATE:
|
||||
case eventTypes.GENERATION_UPDATE:
|
||||
return "observation";
|
||||
case eventTypes.SCORE_CREATE:
|
||||
return "score";
|
||||
case eventTypes.SDK_LOG:
|
||||
return "sdk_log";
|
||||
default:
|
||||
throw new LangfuseNotFoundError(`Unknown event type: ${eventType}`);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export const ClickhouseTableNames = {
|
||||
traces: "traces",
|
||||
observations: "observations",
|
||||
scores: "scores",
|
||||
} as const;
|
||||
|
||||
export type ClickhouseTableName = keyof typeof ClickhouseTableNames;
|
||||
@@ -1,167 +0,0 @@
|
||||
import z from "zod";
|
||||
|
||||
export const clickhouseStringDateSchema = z
|
||||
.string()
|
||||
// clickhouse stores UTC like '2024-05-23 18:33:41.602000'
|
||||
// we need to convert it to '2024-05-23T18:33:41.602000Z'
|
||||
.transform((str) => str.replace(" ", "T") + "Z")
|
||||
.pipe(z.string().datetime());
|
||||
|
||||
export const observationRecordBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
trace_id: z.string().nullish(),
|
||||
project_id: z.string(),
|
||||
type: z.string(),
|
||||
parent_observation_id: z.string().nullish(),
|
||||
name: z.string().nullish(),
|
||||
metadata: z.record(z.string()),
|
||||
level: z.string().nullish(),
|
||||
status_message: z.string().nullish(),
|
||||
version: z.string().nullish(),
|
||||
input: z.string().nullish(),
|
||||
output: z.string().nullish(),
|
||||
provided_model_name: z.string().nullish(),
|
||||
internal_model_id: z.string().nullish(),
|
||||
model_parameters: z.string().nullish(),
|
||||
unit: z.string().nullish(),
|
||||
input_usage_units: z.number().nullish(),
|
||||
output_usage_units: z.number().nullish(),
|
||||
total_usage_units: z.number().nullish(),
|
||||
input_cost: z.number().nullish(),
|
||||
output_cost: z.number().nullish(),
|
||||
total_cost: z.number().nullish(),
|
||||
provided_input_usage_units: z.number().nullish(),
|
||||
provided_output_usage_units: z.number().nullish(),
|
||||
provided_total_usage_units: z.number().nullish(),
|
||||
provided_input_cost: z.number().nullish(),
|
||||
provided_output_cost: z.number().nullish(),
|
||||
provided_total_cost: z.number().nullish(),
|
||||
prompt_id: z.string().nullish(),
|
||||
prompt_name: z.string().nullish(),
|
||||
prompt_version: z.number().nullish(),
|
||||
});
|
||||
export type ObservationRecordBaseType = z.infer<
|
||||
typeof observationRecordBaseSchema
|
||||
>;
|
||||
|
||||
export const observationRecordReadSchema = observationRecordBaseSchema.extend({
|
||||
created_at: clickhouseStringDateSchema,
|
||||
updated_at: clickhouseStringDateSchema,
|
||||
start_time: clickhouseStringDateSchema,
|
||||
end_time: clickhouseStringDateSchema.nullish(),
|
||||
completion_start_time: clickhouseStringDateSchema.nullish(),
|
||||
});
|
||||
export type ObservationRecordReadType = z.infer<
|
||||
typeof observationRecordReadSchema
|
||||
>;
|
||||
|
||||
export const observationRecordInsertSchema = observationRecordBaseSchema.extend(
|
||||
{
|
||||
created_at: z.number(),
|
||||
updated_at: z.number(),
|
||||
start_time: z.number(),
|
||||
end_time: z.number().nullish(),
|
||||
completion_start_time: z.number().nullish(),
|
||||
}
|
||||
);
|
||||
export type ObservationRecordInsertType = z.infer<
|
||||
typeof observationRecordInsertSchema
|
||||
>;
|
||||
|
||||
export const traceRecordBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().nullish(),
|
||||
user_id: z.string().nullish(),
|
||||
metadata: z.record(z.string()),
|
||||
release: z.string().nullish(),
|
||||
version: z.string().nullish(),
|
||||
project_id: z.string(),
|
||||
public: z.boolean(),
|
||||
bookmarked: z.boolean(),
|
||||
tags: z.array(z.string()),
|
||||
input: z.string().nullish(),
|
||||
output: z.string().nullish(),
|
||||
session_id: z.string().nullish(),
|
||||
});
|
||||
export type TraceRecordBaseType = z.infer<typeof traceRecordBaseSchema>;
|
||||
|
||||
export const traceRecordReadSchema = traceRecordBaseSchema.extend({
|
||||
timestamp: clickhouseStringDateSchema,
|
||||
created_at: clickhouseStringDateSchema,
|
||||
updated_at: clickhouseStringDateSchema,
|
||||
});
|
||||
export type TraceRecordReadType = z.infer<typeof traceRecordReadSchema>;
|
||||
|
||||
export const traceRecordInsertSchema = traceRecordBaseSchema.extend({
|
||||
timestamp: z.number(),
|
||||
created_at: z.number(),
|
||||
updated_at: z.number(),
|
||||
});
|
||||
export type TraceRecordInsertType = z.infer<typeof traceRecordInsertSchema>;
|
||||
|
||||
export const scoreRecordBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
project_id: z.string(),
|
||||
trace_id: z.string(),
|
||||
observation_id: z.string().nullish(),
|
||||
name: z.string().nullish(),
|
||||
value: z.union([z.number(), z.string()]).nullish(),
|
||||
source: z.string(),
|
||||
comment: z.string().nullish(),
|
||||
author_user_id: z.string().nullish(),
|
||||
config_id: z.string().nullish(),
|
||||
data_type: z.enum(["NUMERIC", "CATEGORICAL", "BOOLEAN"]).nullish(),
|
||||
string_value: z.string().nullish(),
|
||||
});
|
||||
export type ScoreRecordBaseType = z.infer<typeof scoreRecordBaseSchema>;
|
||||
|
||||
export const scoreRecordReadSchema = scoreRecordBaseSchema.extend({
|
||||
created_at: clickhouseStringDateSchema,
|
||||
updated_at: clickhouseStringDateSchema,
|
||||
timestamp: clickhouseStringDateSchema,
|
||||
});
|
||||
export type ScoreRecordReadType = z.infer<typeof scoreRecordReadSchema>;
|
||||
|
||||
export const scoreRecordInsertSchema = scoreRecordBaseSchema.extend({
|
||||
created_at: z.number(),
|
||||
updated_at: z.number(),
|
||||
timestamp: z.number(),
|
||||
});
|
||||
export type ScoreRecordInsertType = z.infer<typeof scoreRecordInsertSchema>;
|
||||
|
||||
export const convertTraceReadToInsert = (
|
||||
record: TraceRecordReadType
|
||||
): TraceRecordInsertType => {
|
||||
return {
|
||||
...record,
|
||||
created_at: new Date(record.created_at).getTime(),
|
||||
updated_at: new Date(record.created_at).getTime(),
|
||||
timestamp: new Date(record.timestamp).getTime(),
|
||||
};
|
||||
};
|
||||
|
||||
export const convertObservationReadToInsert = (
|
||||
record: ObservationRecordReadType
|
||||
): ObservationRecordInsertType => {
|
||||
return {
|
||||
...record,
|
||||
created_at: new Date(record.created_at).getTime(),
|
||||
updated_at: new Date(record.created_at).getTime(),
|
||||
start_time: new Date(record.start_time).getTime(),
|
||||
end_time: record.end_time ? new Date(record.end_time).getTime() : undefined,
|
||||
completion_start_time: record.completion_start_time
|
||||
? new Date(record.completion_start_time).getTime()
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const convertScoreReadToInsert = (
|
||||
record: ScoreRecordReadType
|
||||
): ScoreRecordInsertType => {
|
||||
return {
|
||||
...record,
|
||||
created_at: new Date(record.created_at).getTime(),
|
||||
updated_at: new Date(record.updated_at).getTime(),
|
||||
timestamp: new Date(record.timestamp).getTime(),
|
||||
};
|
||||
};
|
||||
+18
-17
@@ -1,8 +1,9 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { ColumnDefinition, type TableNames } from "./tableDefinitions";
|
||||
import { FilterState } from "./types";
|
||||
import { filterOperators, timeFilter } from "./interfaces/filters";
|
||||
import { ColumnDefinition, type TableNames } from "../tableDefinitions";
|
||||
import { FilterState } from "../types";
|
||||
import { filterOperators, timeFilter } from "../interfaces/filters";
|
||||
import { z } from "zod";
|
||||
import { logger } from "./index";
|
||||
|
||||
const operatorReplacements = {
|
||||
"any of": "IN",
|
||||
@@ -25,7 +26,7 @@ const arrayOperatorReplacements = {
|
||||
export function tableColumnsToSqlFilterAndPrefix(
|
||||
filters: FilterState,
|
||||
tableColumns: ColumnDefinition[],
|
||||
table: TableNames
|
||||
table: TableNames,
|
||||
): Prisma.Sql {
|
||||
const sql = tableColumnsToSqlFilter(filters, tableColumns, table);
|
||||
if (sql === Prisma.empty) {
|
||||
@@ -41,17 +42,17 @@ export function tableColumnsToSqlFilterAndPrefix(
|
||||
export function tableColumnsToSqlFilter(
|
||||
filters: FilterState,
|
||||
tableColumns: ColumnDefinition[],
|
||||
table: TableNames
|
||||
table: TableNames,
|
||||
): Prisma.Sql {
|
||||
const internalFilters = filters.map((filter) => {
|
||||
// Get column definition to map column to internal name, e.g. "t.id"
|
||||
const col = tableColumns.find(
|
||||
(c) =>
|
||||
// TODO: Only use id instead of name
|
||||
c.name === filter.column || c.id === filter.column
|
||||
c.name === filter.column || c.id === filter.column,
|
||||
);
|
||||
if (!col) {
|
||||
console.error("Invalid filter column", filter.column);
|
||||
logger.error("Invalid filter column", filter.column);
|
||||
throw new Error("Invalid filter column: " + filter.column);
|
||||
}
|
||||
const colPrisma = Prisma.raw(col.internal);
|
||||
@@ -70,13 +71,13 @@ export function tableColumnsToSqlFilter(
|
||||
? Prisma.raw(
|
||||
arrayOperatorReplacements[
|
||||
filter.operator as keyof typeof arrayOperatorReplacements
|
||||
]
|
||||
],
|
||||
)
|
||||
: filter.operator in operatorReplacements
|
||||
? Prisma.raw(
|
||||
operatorReplacements[
|
||||
filter.operator as keyof typeof operatorReplacements
|
||||
]
|
||||
],
|
||||
)
|
||||
: Prisma.raw(filter.operator); //checked by zod
|
||||
|
||||
@@ -96,13 +97,13 @@ export function tableColumnsToSqlFilter(
|
||||
break;
|
||||
case "stringOptions":
|
||||
valuePrisma = Prisma.sql`(${Prisma.join(
|
||||
filter.value.map((v) => Prisma.sql`${v}`)
|
||||
filter.value.map((v) => Prisma.sql`${v}`),
|
||||
)})`;
|
||||
break;
|
||||
case "arrayOptions":
|
||||
valuePrisma = Prisma.sql`ARRAY[${Prisma.join(
|
||||
filter.value.map((v) => Prisma.sql`${v}`),
|
||||
", "
|
||||
", ",
|
||||
)}] `;
|
||||
break;
|
||||
|
||||
@@ -122,12 +123,12 @@ export function tableColumnsToSqlFilter(
|
||||
filter.type === "string" || filter.type === "stringObject"
|
||||
? [
|
||||
["contains", "does not contain", "ends with"].includes(
|
||||
filter.operator
|
||||
filter.operator,
|
||||
)
|
||||
? Prisma.raw("'%' || ")
|
||||
: Prisma.empty,
|
||||
["contains", "does not contain", "starts with"].includes(
|
||||
filter.operator
|
||||
filter.operator,
|
||||
)
|
||||
? Prisma.raw(" || '%'")
|
||||
: Prisma.empty,
|
||||
@@ -151,7 +152,7 @@ export function tableColumnsToSqlFilter(
|
||||
|
||||
const castValueToPostgresTypes = (
|
||||
column: ColumnDefinition,
|
||||
table: TableNames
|
||||
table: TableNames,
|
||||
) => {
|
||||
return column.name === "type" &&
|
||||
(table === "observations" ||
|
||||
@@ -167,7 +168,7 @@ const dateOperators = filterOperators["datetime"];
|
||||
export const datetimeFilterToPrismaSql = (
|
||||
safeColumn: string,
|
||||
operator: (typeof dateOperators)[number],
|
||||
value: Date
|
||||
value: Date,
|
||||
) => {
|
||||
if (!dateOperators.includes(operator)) {
|
||||
throw new Error("Invalid operator: " + operator);
|
||||
@@ -177,12 +178,12 @@ export const datetimeFilterToPrismaSql = (
|
||||
}
|
||||
|
||||
return Prisma.sql`AND ${Prisma.raw(safeColumn)} ${Prisma.raw(
|
||||
operator
|
||||
operator,
|
||||
)} ${value}::timestamp with time zone at time zone 'UTC'`;
|
||||
};
|
||||
|
||||
export const datetimeFilterToPrisma = (
|
||||
timestampFilter: z.infer<typeof timeFilter>
|
||||
timestampFilter: z.infer<typeof timeFilter>,
|
||||
) => {
|
||||
const prismaTimestampFilter =
|
||||
timestampFilter.operator === ">="
|
||||
@@ -3,25 +3,32 @@ export * from "./services/email/organizationInvitation/sendMembershipInvitationE
|
||||
export * from "./services/email/batchExportSuccess/sendBatchExportSuccessEmail";
|
||||
export * from "./services/email/passwordReset/sendResetPasswordVerificationRequest";
|
||||
export * from "./services/PromptService";
|
||||
export * from "./auth/auth";
|
||||
export * from "./auth/apiKeys";
|
||||
export * from "./auth/customSsoProvider";
|
||||
export * from "./llm/fetchLLMCompletion";
|
||||
export * from "./llm/types";
|
||||
export * from "./utils/DatabaseReadStream";
|
||||
export * from "./utils/transforms";
|
||||
export * from "./clickhouse";
|
||||
export * from "../server/definitions";
|
||||
export * from "../server/ingestion/IngestionUtils";
|
||||
export * from "./clickhouse/client";
|
||||
export * from "./clickhouse/schema-utils";
|
||||
export * from "./clickhouse/schema";
|
||||
export * from "./repositories/definitions";
|
||||
export * from "../server/ingestion/types";
|
||||
export * from "../server/ingestion/model-match";
|
||||
export * from "./ingestion/modelMatch";
|
||||
export * from "../server/ingestion/types";
|
||||
export * from "../server/ingestion/validateAndInflateScore";
|
||||
export * from "./redis/redis";
|
||||
export * from "./redis/trace-upsert";
|
||||
export * from "./redis/batch-export";
|
||||
export * from "./redis/ingestionFlushQueue";
|
||||
export * from "./redis/legacy-ingestion";
|
||||
export * from "./redis/traceUpsert";
|
||||
export * from "./redis/batchExport";
|
||||
export * from "./redis/legacyIngestion";
|
||||
export * from "./redis/ingestionQueue";
|
||||
export * from "./auth/types";
|
||||
export * from "./ingestion/legacy/index";
|
||||
export * from "./queues";
|
||||
export * from "./ingestion/legacy/EventProcessor";
|
||||
export * from "./orderByToPrisma";
|
||||
export * from "./filterToPrisma";
|
||||
export * from "./instrumentation";
|
||||
export * from "./logger";
|
||||
export * from "./queries";
|
||||
export * from "./repositories";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user