Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30ca47b667 | ||
|
|
ce1c1a5016 | ||
|
|
ed01cb7d91 | ||
|
|
17364919af | ||
|
|
0c58edcf5e | ||
|
|
90d7c7df98 | ||
|
|
07fbae3da7 | ||
|
|
b4bd8208c3 | ||
|
|
c8b282dc82 | ||
|
|
e2922aa787 | ||
|
|
e8690eeb0b | ||
|
|
a695d58e8e | ||
|
|
b097a98444 | ||
|
|
87c6d45a01 | ||
|
|
d5a79b1830 | ||
|
|
40b967c258 | ||
|
|
b6b7a7c670 | ||
|
|
346e787784 | ||
|
|
8e67f851b0 | ||
|
|
8b416da2ef | ||
|
|
88237dcb30 | ||
|
|
8a0b3962ac | ||
|
|
2f6414bbf4 | ||
|
|
c7aa992586 | ||
|
|
c2b5923e88 | ||
|
|
fc411d0c5c | ||
|
|
3fac8d2608 | ||
|
|
cd413fa887 | ||
|
|
be7cc835be | ||
|
|
b68896d14a | ||
|
|
aae1909466 | ||
|
|
3d7960f43e | ||
|
|
a121e24b79 | ||
|
|
7279a924ed | ||
|
|
a10951b7e5 | ||
|
|
e456b08f92 | ||
|
|
85130cecb3 | ||
|
|
fbb15e26ec | ||
|
|
dd203a1f18 | ||
|
|
06c25f9ec3 | ||
|
|
7072ca2786 | ||
|
|
cb1e5f4c19 | ||
|
|
d4a28aa164 | ||
|
|
fda5fcdd0c | ||
|
|
24131e0791 | ||
|
|
ffe132403e | ||
|
|
cce3bf35e1 | ||
|
|
eccfec36e8 | ||
|
|
a71faa4e60 | ||
|
|
b7362258c6 | ||
|
|
5eb1d98985 | ||
|
|
1c574b2a5d | ||
|
|
6db9b87b6a | ||
|
|
3e3ccb65ff | ||
|
|
0d53b443e5 | ||
|
|
1b795a3dcf | ||
|
|
2e94bc9e09 | ||
|
|
8b94e8a3bd | ||
|
|
91730a2eaf | ||
|
|
4f1a2337be | ||
|
|
4cf5aa03ec | ||
|
|
922eafd4f6 | ||
|
|
9067f80206 | ||
|
|
a7ca1fb269 | ||
|
|
b51f321d22 | ||
|
|
dd048a64a0 | ||
|
|
c6c19de0e8 | ||
|
|
5491f176e1 | ||
|
|
7a28473ebb | ||
|
|
005ce7318c | ||
|
|
448c56e114 | ||
|
|
1d6e498e2b | ||
|
|
33ab717ae0 | ||
|
|
ec563b775b | ||
|
|
de427b5572 | ||
|
|
95921fa5da | ||
|
|
9e4d352366 | ||
|
|
02449cbe0a | ||
|
|
616c68a87b | ||
|
|
319b22ae78 | ||
|
|
b919562eed | ||
|
|
91c01e7363 | ||
|
|
48ec1bccae | ||
|
|
66aa2fd48e | ||
|
|
f822bf1049 | ||
|
|
96ec24aad8 | ||
|
|
068fbd0cc6 | ||
|
|
7c8b76d573 | ||
|
|
e4e93d64bb | ||
|
|
f5cf8693e9 | ||
|
|
5beef28214 | ||
|
|
d93712c6ab | ||
|
|
b1b0a966be | ||
|
|
b5edadb203 | ||
|
|
3090882590 | ||
|
|
787f5d8d10 | ||
|
|
5fa142eda4 | ||
|
|
d96865b019 | ||
|
|
8d808923a1 | ||
|
|
0f056c1c2d | ||
|
|
094a523795 | ||
|
|
c60e58a568 | ||
|
|
6147e9c518 | ||
|
|
f0408b208e | ||
|
|
55e0bdcac0 | ||
|
|
05e15c9f53 | ||
|
|
ce6249b266 | ||
|
|
83e91e5bf5 | ||
|
|
edf073cc18 | ||
|
|
65343a8e9b | ||
|
|
0978055bdc | ||
|
|
76aaf2a3d0 | ||
|
|
8ed61bf705 | ||
|
|
45436530b4 | ||
|
|
7a3f01fc0a | ||
|
|
468fc4d079 | ||
|
|
945d13dac0 | ||
|
|
3646eaa5a8 | ||
|
|
feb5af11b8 | ||
|
|
3711af5663 | ||
|
|
9ec798f393 | ||
|
|
f71fd43d91 | ||
|
|
0303596420 | ||
|
|
cfaccc46d3 | ||
|
|
907f764adc | ||
|
|
652e9e3fe5 | ||
|
|
48673e43d7 | ||
|
|
a136ac8184 | ||
|
|
9ebf62fc9c | ||
|
|
cf52dce773 | ||
|
|
0a6ecf1cba | ||
|
|
c359900b59 | ||
|
|
720ed7a36f | ||
|
|
1dbefb0994 | ||
|
|
abfdd2366a | ||
|
|
1f9bb37f3b | ||
|
|
00afab9725 | ||
|
|
d04e808515 | ||
|
|
ee04912f0e | ||
|
|
7f04a54c0c | ||
|
|
cce11ef6b5 | ||
|
|
1b03f537e2 | ||
|
|
dba114e4a3 | ||
|
|
fefe3bc3bd | ||
|
|
800cc299cb | ||
|
|
1bdaed4314 | ||
|
|
71dde233c5 | ||
|
|
3b80afdf2d | ||
|
|
b69f0bda9d | ||
|
|
9b9a58ae45 | ||
|
|
775e6a8bfc | ||
|
|
5253014fa6 | ||
|
|
1571de133b | ||
|
|
9202f4ec79 | ||
|
|
949e3d1181 | ||
|
|
2d280329c5 | ||
|
|
6e1886055a | ||
|
|
39e4a7533a | ||
|
|
e8787e3f38 | ||
|
|
59055cd311 | ||
|
|
7712b63b95 | ||
|
|
f25d466388 | ||
|
|
b9e18f59d8 | ||
|
|
09f3343db2 | ||
|
|
5ab8e4219f | ||
|
|
40359f0e3f | ||
|
|
817b6d37bc | ||
|
|
8bb73822d3 | ||
|
|
fa53ba3c1a | ||
|
|
086a764083 | ||
|
|
79a9f3c3e3 | ||
|
|
abd46f2d4d | ||
|
|
dfd9565a3f | ||
|
|
ebb8a5a13f | ||
|
|
44104596f6 | ||
|
|
17f6260671 | ||
|
|
0a2bfe5ab5 | ||
|
|
8bd1a4da3e | ||
|
|
31b230d3e0 | ||
|
|
dbe71249e7 | ||
|
|
a9d0486edd | ||
|
|
2bf506a436 | ||
|
|
edca91c12a | ||
|
|
a11e7ddfee | ||
|
|
cb9eb23f36 | ||
|
|
5e942f0208 | ||
|
|
8e0e139848 | ||
|
|
acc1227962 | ||
|
|
d9682511c1 | ||
|
|
00e0b51bc4 | ||
|
|
e63cf84e4f | ||
|
|
63d1051ac2 | ||
|
|
0e056eb072 | ||
|
|
87f7095966 | ||
|
|
5eefe997ae | ||
|
|
e251e60b53 | ||
|
|
adc05eeef4 | ||
|
|
702143fbe2 | ||
|
|
06643f30e5 | ||
|
|
2e496d5429 | ||
|
|
5b62df9122 | ||
|
|
fd189dc66b | ||
|
|
10a3644dcf | ||
|
|
8ca000b225 | ||
|
|
47d41b6a42 | ||
|
|
1d66408c18 | ||
|
|
44d1727dd0 | ||
|
|
bfb2878719 | ||
|
|
67e6b4ce87 | ||
|
|
dcc5bf9c6e | ||
|
|
db357101b7 | ||
|
|
afbd45b9c3 | ||
|
|
b6b37212ce | ||
|
|
76a090241e | ||
|
|
c53abf6aeb | ||
|
|
5cbfd922f9 | ||
|
|
0be269d519 | ||
|
|
d0eeb44b90 | ||
|
|
30ced3ab42 | ||
|
|
2ed6b5a40f | ||
|
|
6eaef75f20 | ||
|
|
31c36565c3 | ||
|
|
d1a1ca7744 | ||
|
|
2dac75d203 | ||
|
|
fab90b36a6 | ||
|
|
597bd6c96b | ||
|
|
ba3bcc9295 | ||
|
|
5f65929a5e | ||
|
|
f1344a4c1a | ||
|
|
4e3b9a82f7 | ||
|
|
7b803619fd | ||
|
|
1ef51b1644 | ||
|
|
fee9e34b7e | ||
|
|
1354db489f | ||
|
|
740a25150c | ||
|
|
47c2d65d12 | ||
|
|
d94e7c1c06 | ||
|
|
38ed98a76d | ||
|
|
92567b765b | ||
|
|
b37396893a | ||
|
|
0fd1eebbff | ||
|
|
f478bb5763 |
@@ -0,0 +1,47 @@
|
||||
FROM node:20
|
||||
|
||||
# ---------- System packages --------------------------------------------------
|
||||
# The buildpack-deps base already ships git, build-essential, python, etc.
|
||||
# Add a few extra tools handy during Langfuse development.
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
openssl \
|
||||
wget \
|
||||
curl \
|
||||
ca-certificates \
|
||||
postgresql-client \
|
||||
redis-tools \
|
||||
less nano \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ---------- pnpm -------------------------------------------------------------
|
||||
# Langfuse monorepo relies on pnpm 9.5.0 (see CONTRIBUTING.md)
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && \
|
||||
corepack prepare pnpm@9.5.0 --activate
|
||||
|
||||
# ---------- golang-migrate ----------------------------------------------------
|
||||
# CLI used for database migrations during development.
|
||||
ENV MIGRATE_VERSION=4.18.2
|
||||
RUN wget -qO- "https://github.com/golang-migrate/migrate/releases/download/v${MIGRATE_VERSION}/migrate.linux-amd64.tar.gz" \
|
||||
| tar -xz -C /usr/local/bin && \
|
||||
chmod +x /usr/local/bin/migrate
|
||||
|
||||
# ---------- Non-root user -----------------------------------------------------
|
||||
# Use root for convenience in development containers
|
||||
WORKDIR /workspace
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -ms /bin/bash ubuntu
|
||||
|
||||
# Pre-create pnpm store and set correct ownership to avoid first-run cost & permission issues
|
||||
RUN pnpm store path > /dev/null && \
|
||||
chown -R ubuntu:ubuntu /pnpm
|
||||
|
||||
USER ubuntu
|
||||
WORKDIR /home/ubuntu
|
||||
ENV HOME=/home/ubuntu
|
||||
|
||||
# Container starts with a bash shell ready for hacking.
|
||||
CMD ["bash"]
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"install": "cp .env.dev.example .env && pnpm i",
|
||||
"build": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"start": "pnpm dx-f"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# General rules
|
||||
|
||||
- Linting in this repo only works if the development server is running
|
||||
@@ -0,0 +1,84 @@
|
||||
# When adding additional environment variables, the schema in "/src/env.mjs"
|
||||
# should be updated accordingly.
|
||||
|
||||
# Prisma
|
||||
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
|
||||
DIRECT_URL="postgresql://postgres:postgres@localhost:5432/postgres"
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
|
||||
|
||||
# Clickhouse
|
||||
CLICKHOUSE_MIGRATION_URL="clickhouse://localhost:9000"
|
||||
CLICKHOUSE_URL="http://localhost:8123"
|
||||
CLICKHOUSE_USER="clickhouse"
|
||||
CLICKHOUSE_PASSWORD="clickhouse"
|
||||
CLICKHOUSE_CLUSTER_ENABLED="false"
|
||||
|
||||
# Next Auth
|
||||
# You can generate a new secret on the command line with:
|
||||
# openssl rand -base64 32
|
||||
# https://next-auth.js.org/configuration/options#secret
|
||||
# NEXTAUTH_SECRET=""
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
NEXTAUTH_SECRET="secret"
|
||||
|
||||
# Langfuse Cloud Environment
|
||||
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION="DEV"
|
||||
|
||||
# Langfuse experimental features
|
||||
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES="true"
|
||||
|
||||
# Salt for API key hashing
|
||||
SALT="salt"
|
||||
|
||||
# Email
|
||||
EMAIL_FROM_ADDRESS="" # Defines the email address to use as the from address.
|
||||
SMTP_CONNECTION_URL="" # Defines the connection url for smtp server.
|
||||
|
||||
# S3 Batch Exports
|
||||
LANGFUSE_S3_BATCH_EXPORT_ENABLED=true
|
||||
LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse
|
||||
LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio
|
||||
LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=miniosecret
|
||||
LANGFUSE_S3_BATCH_EXPORT_REGION=us-east-1
|
||||
LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://localhost:9090
|
||||
## Necessary for minio compatibility
|
||||
LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true
|
||||
LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/
|
||||
|
||||
# S3 Media Upload LOCAL
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=miniosecret
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_REGION=us-east-1
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090
|
||||
## Necessary for minio compatibility
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/
|
||||
|
||||
# S3 Event Bucket Upload
|
||||
## Set to true to test uploading all events to S3
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio
|
||||
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=miniosecret
|
||||
LANGFUSE_S3_EVENT_UPLOAD_REGION=us-east-1
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://localhost:9090
|
||||
## Necessary for minio compatibility
|
||||
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
|
||||
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
|
||||
|
||||
# Set during docker build of application
|
||||
# Used to disable environment verification at build time
|
||||
# DOCKER_BUILD=1
|
||||
|
||||
REDIS_HOST="127.0.0.1"
|
||||
REDIS_PORT=6379
|
||||
REDIS_AUTH="bitnami"
|
||||
REDIS_CLUSTER_ENABLED="true"
|
||||
REDIS_CLUSTER_NODES="127.0.0.1:6370,127.0.0.1:6371,127.0.0.1:6372,127.0.0.1:6373,127.0.0.1:6374,127.0.0.1:6375"
|
||||
LANGFUSE_INGESTION_QUEUE_SHARD_COUNT=8
|
||||
|
||||
# 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"
|
||||
+8
-4
@@ -157,10 +157,6 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# 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
|
||||
@@ -179,6 +175,10 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# REDIS_CONNECTION_STRING=
|
||||
# REDIS_ENABLE_AUTO_PIPELINING=
|
||||
|
||||
# Redis Cluster configuration (optional)
|
||||
# REDIS_CLUSTER_ENABLED=false
|
||||
# REDIS_CLUSTER_NODES=redis-node1:6379,redis-node2:6379,redis-node3:6379
|
||||
|
||||
# Cache configuration
|
||||
# LANGFUSE_CACHE_API_KEY_ENABLED=
|
||||
# LANGFUSE_CACHE_API_KEY_TTL_SECONDS=
|
||||
@@ -215,6 +215,8 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# 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
|
||||
# LANGFUSE_UI_VISIBLE_PRODUCT_MODULES=
|
||||
# LANGFUSE_UI_HIDDEN_PRODUCT_MODULES=
|
||||
|
||||
### END Enterprise Edition Configuration
|
||||
|
||||
@@ -253,6 +255,8 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# Plain Chat
|
||||
# NEXT_PUBLIC_PLAIN_APP_ID=
|
||||
# PLAIN_AUTHENTICATION_SECRET=
|
||||
# PLAIN_API_KEY=
|
||||
# PLAIN_CARDS_API_TOKEN=
|
||||
|
||||
# Admin API
|
||||
# ADMIN_API_KEY=
|
||||
|
||||
@@ -15,6 +15,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.environment }}
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
- name: Get app name
|
||||
uses: winterjung/split@v2
|
||||
id: split
|
||||
|
||||
@@ -171,12 +171,12 @@ jobs:
|
||||
needs:
|
||||
- pre-job
|
||||
if: needs.pre-job.outputs.should_skip != 'true'
|
||||
name: tests-web-async (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.blob-provider }})
|
||||
name: tests-web-async (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.deploy-mode }})
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
postgres-version: [12, 15]
|
||||
blob-provider: ["", "-azure"]
|
||||
deploy-mode: ["", "-azure", "-redis-cluster"]
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
@@ -208,8 +208,8 @@ jobs:
|
||||
pnpm install
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev${{ matrix.blob-provider }}.example .env
|
||||
grep -v -e '^LANGFUSE_S3_BATCH_EXPORT_ENABLED=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev${{ matrix.blob-provider }}.example > .env
|
||||
cp .env.dev${{ matrix.deploy-mode }}.example .env
|
||||
grep -v -e '^LANGFUSE_S3_BATCH_EXPORT_ENABLED=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev${{ matrix.deploy-mode }}.example > .env
|
||||
echo "LANGFUSE_INGESTION_QUEUE_DELAY_MS=1" >> .env
|
||||
echo "LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1" >> .env
|
||||
echo "LANGFUSE_TRACE_DELETE_CONCURRENCY=100" >> .env
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
echo "LANGFUSE_EE_LICENSE_KEY=langfuse_ee_test" >> .env
|
||||
- name: Run dev containers
|
||||
run: |
|
||||
docker compose -f docker-compose.dev${{ matrix.blob-provider }}.yml up -d
|
||||
docker compose -f docker-compose.dev${{ matrix.deploy-mode }}.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
docker compose ps
|
||||
env:
|
||||
@@ -250,12 +250,12 @@ jobs:
|
||||
needs:
|
||||
- pre-job
|
||||
if: needs.pre-job.outputs.should_skip != 'true'
|
||||
name: tests-worker (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.blob-provider }})
|
||||
name: tests-worker (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.deploy-mode }})
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
postgres-version: [12, 15]
|
||||
blob-provider: ["", "-azure"]
|
||||
deploy-mode: ["", "-azure", "-redis-cluster"]
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
@@ -287,12 +287,12 @@ jobs:
|
||||
which migrate
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev${{ matrix.blob-provider }}.example .env
|
||||
cp .env.dev${{ matrix.blob-provider }}.example web/.env
|
||||
cp .env.dev${{ matrix.blob-provider }}.example worker/.env
|
||||
cp .env.dev${{ matrix.deploy-mode }}.example .env
|
||||
cp .env.dev${{ matrix.deploy-mode }}.example web/.env
|
||||
cp .env.dev${{ matrix.deploy-mode }}.example worker/.env
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker compose -f docker-compose.dev${{ matrix.blob-provider }}.yml up -d
|
||||
docker compose -f docker-compose.dev${{ matrix.deploy-mode }}.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
docker compose ps
|
||||
- name: Ensure no unhealthy status
|
||||
|
||||
@@ -39,6 +39,7 @@ yarn-error.log*
|
||||
.env*
|
||||
!.env.dev.example
|
||||
!.env.dev-azure.example
|
||||
!.env.dev-redis-cluster.example
|
||||
!.env.prod.example
|
||||
|
||||
# vercel
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Codex Guidelines for Langfuse
|
||||
|
||||
Langfuse is an **open source LLM engineering** platform for developing, monitoring, evaluating and debugging AI applications. See the README for more details.
|
||||
|
||||
## Linting
|
||||
- Run `pnpm run lint` to lint all packages.
|
||||
- Fix issues automatically with `pnpm run lint:fix`.
|
||||
|
||||
## Tests
|
||||
- Codex cannot run the test suite because it depends on Docker-based infrastructure that is unavailable in this environment.
|
||||
|
||||
## Cursor Rules
|
||||
- Additional folder-specific rules live in `.cursor/rules/`.
|
||||
|
||||
## Commits
|
||||
- Follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) when crafting commit messages.
|
||||
+2
-2
@@ -213,8 +213,8 @@ LANGFUSE_HOST="https://cloud.langfuse.com" # 🇪🇺 欧盟区域
|
||||
|
||||
创建示例代码(文件名:**main.py**):
|
||||
|
||||
````python:main.py
|
||||
from langfuse.decorators import observe
|
||||
```python:main.py
|
||||
from langfuse import observe
|
||||
from langfuse.openai import openai # OpenAI 集成
|
||||
|
||||
@observe()
|
||||
|
||||
+1
-1
@@ -219,7 +219,7 @@ LANGFUSE_HOST="https://cloud.langfuse.com" # 🇪🇺 EUリージョン
|
||||
```
|
||||
|
||||
```python:/@observe()/ /from langfuse.openai import openai/ filename="main.py"
|
||||
from langfuse.decorators import observe
|
||||
from langfuse import observe
|
||||
from langfuse.openai import openai # OpenAI統合
|
||||
|
||||
@observe()
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ LANGFUSE_HOST="https://cloud.langfuse.com" # 🇪🇺 EU region
|
||||
```
|
||||
|
||||
```python:main.py
|
||||
from langfuse.decorators import observe
|
||||
from langfuse import observe
|
||||
from langfuse.openai import openai # OpenAI integration
|
||||
|
||||
@observe()
|
||||
|
||||
@@ -93,7 +93,7 @@ Langfuse is an **open source LLM engineering** platform. It helps teams collabor
|
||||
|
||||
### Langfuse Cloud
|
||||
|
||||
Managed deployment by the Langfuse team, generous free-tier (hobby plan), no credit card required.
|
||||
Managed deployment by the Langfuse team, generous free-tier, no credit card required.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://cloud.langfuse.com" target="_blank">
|
||||
@@ -115,12 +115,11 @@ Run Langfuse on your own infrastructure:
|
||||
# Run the langfuse docker compose
|
||||
docker compose up
|
||||
```
|
||||
|
||||
- [Kubernetes (Helm)](https://langfuse.com/self-hosting/kubernetes-helm): Run Langfuse on a Kubernetes cluster using Helm. This is the preferred production deployment.
|
||||
- [VM](https://langfuse.com/self-hosting/docker-compose): Run Langfuse on a single Virtual Machine using Docker Compose.
|
||||
- Planned: Cloud-specific deployment guides, please upvote and comment on the following threads: [AWS](https://github.com/orgs/langfuse/discussions/4645), [Google Cloud](https://github.com/orgs/langfuse/discussions/4646), [Azure](https://github.com/orgs/langfuse/discussions/4647).
|
||||
- [Kubernetes (Helm)](https://langfuse.com/self-hosting/kubernetes-helm): Run Langfuse on a Kubernetes cluster using Helm. This is the preferred production deployment.
|
||||
- Terraform Templates: [AWS](https://langfuse.com/self-hosting/aws), [Azure](https://langfuse.com/self-hosting/azure), [GCP](https://langfuse.com/self-hosting/gcp)
|
||||
|
||||
See [self-hosting documentation](https://langfuse.com/self-hosting) to learn more about the architecture and configuration options.
|
||||
See [self-hosting documentation](https://langfuse.com/self-hosting) to learn more about architecture and configuration options.
|
||||
|
||||
## 🔌 Integrations
|
||||
|
||||
@@ -191,7 +190,7 @@ LANGFUSE_HOST="https://cloud.langfuse.com" # 🇪🇺 EU region
|
||||
```
|
||||
|
||||
```python /@observe()/ /from langfuse.openai import openai/ filename="main.py"
|
||||
from langfuse.decorators import observe
|
||||
from langfuse import observe
|
||||
from langfuse.openai import openai # OpenAI integration
|
||||
|
||||
@observe()
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:24.3
|
||||
user: "101:101"
|
||||
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:
|
||||
- 127.0.0.1:8123:8123
|
||||
- 127.0.0.1:9000:9000
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
minio:
|
||||
image: minio/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:
|
||||
- 127.0.0.1:9090:9000
|
||||
- 127.0.0.1:9091:9001
|
||||
volumes:
|
||||
- langfuse_minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 1s
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
command: ["postgres", "-c", "log_statement=all"]
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
ports:
|
||||
- 127.0.0.1:5432:5432
|
||||
volumes:
|
||||
- langfuse_postgres_data:/var/lib/postgresql/data
|
||||
|
||||
# Redis Cluster. Requires a Unix host to work for network_mode: host. I.e. tests will fail on windows and mac with this setup.
|
||||
redis-node-0:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-0:/bitnami/redis/data
|
||||
environment:
|
||||
REDIS_PASSWORD: bitnami
|
||||
REDIS_PORT_NUMBER: 6370
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-1:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-1:/bitnami/redis/data
|
||||
environment:
|
||||
REDIS_PASSWORD: bitnami
|
||||
REDIS_PORT_NUMBER: 6371
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-2:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-2:/bitnami/redis/data
|
||||
environment:
|
||||
REDIS_PASSWORD: bitnami
|
||||
REDIS_PORT_NUMBER: 6372
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-3:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-3:/bitnami/redis/data
|
||||
environment:
|
||||
REDIS_PASSWORD: bitnami
|
||||
REDIS_PORT_NUMBER: 6373
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-4:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-4:/bitnami/redis/data
|
||||
environment:
|
||||
REDIS_PASSWORD: bitnami
|
||||
REDIS_PORT_NUMBER: 6374
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-5:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-5:/bitnami/redis/data
|
||||
depends_on:
|
||||
- redis-node-0
|
||||
- redis-node-1
|
||||
- redis-node-2
|
||||
- redis-node-3
|
||||
- redis-node-4
|
||||
environment:
|
||||
REDISCLI_AUTH: bitnami
|
||||
REDIS_CLUSTER_REPLICAS: 1
|
||||
REDIS_PASSWORD: bitnami
|
||||
REDIS_PORT_NUMBER: 6375
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
REDIS_CLUSTER_CREATOR: yes
|
||||
|
||||
volumes:
|
||||
langfuse_postgres_data:
|
||||
driver: local
|
||||
langfuse_clickhouse_data:
|
||||
driver: local
|
||||
langfuse_clickhouse_logs:
|
||||
driver: local
|
||||
langfuse_minio_data:
|
||||
driver: local
|
||||
redis-cluster_data-0:
|
||||
driver: local
|
||||
redis-cluster_data-1:
|
||||
driver: local
|
||||
redis-cluster_data-2:
|
||||
driver: local
|
||||
redis-cluster_data-3:
|
||||
driver: local
|
||||
redis-cluster_data-4:
|
||||
driver: local
|
||||
redis-cluster_data-5:
|
||||
driver: local
|
||||
@@ -29,6 +29,7 @@ services:
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME
|
||||
CLICKHOUSE_CLUSTER_ENABLED: ${CLICKHOUSE_CLUSTER_ENABLED:-false}
|
||||
LANGFUSE_USE_AZURE_BLOB: ${LANGFUSE_USE_AZURE_BLOB:-false}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto}
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
|
||||
|
||||
+2
-2
@@ -28,9 +28,9 @@
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"axios": "^1.8.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"next": "^14.2.26",
|
||||
"next": "^14.2.30",
|
||||
"next-auth": "^4.24.11",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import { removeEmptyEnvVariables } from "@langfuse/shared";
|
||||
|
||||
const EnvSchema = z.object({
|
||||
|
||||
@@ -457,13 +457,13 @@ types:
|
||||
docs: Unit used by this model.
|
||||
type: optional<ModelUsageUnit>
|
||||
inputPrice:
|
||||
docs: Price (USD) per input unit
|
||||
docs: Deprecated. See 'prices' instead. Price (USD) per input unit
|
||||
type: optional<double>
|
||||
outputPrice:
|
||||
docs: Price (USD) per output unit
|
||||
docs: Deprecated. See 'prices' instead. Price (USD) per output unit
|
||||
type: optional<double>
|
||||
totalPrice:
|
||||
docs: Price (USD) per total unit. Cannot be set if input or output price is set.
|
||||
docs: Deprecated. See 'prices' instead. Price (USD) per total unit. Cannot be set if input or output price is set.
|
||||
type: optional<double>
|
||||
tokenizerId:
|
||||
docs: Optional. Tokenizer to be applied to observations which match to this model. See docs for more details.
|
||||
@@ -473,6 +473,13 @@ types:
|
||||
type: optional<unknown>
|
||||
isLangfuseManaged:
|
||||
type: boolean
|
||||
prices:
|
||||
docs: Price (USD) by usage type
|
||||
type: map<string, ModelPrice>
|
||||
|
||||
ModelPrice:
|
||||
properties:
|
||||
price: double
|
||||
|
||||
# Utilities
|
||||
ModelUsageUnit:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
pagination: ./utils/pagination.yml
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public
|
||||
@@ -11,6 +12,23 @@ service:
|
||||
path: /dataset-run-items
|
||||
request: CreateDatasetRunItemRequest
|
||||
response: commons.DatasetRunItem
|
||||
list:
|
||||
method: GET
|
||||
docs: List dataset run items
|
||||
path: /dataset-run-items
|
||||
request:
|
||||
name: ListDatasetRunItemsRequest
|
||||
query-parameters:
|
||||
datasetId: string
|
||||
runName: string
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
response: PaginatedDatasetRunItems
|
||||
|
||||
types:
|
||||
CreateDatasetRunItemRequest:
|
||||
properties:
|
||||
@@ -26,3 +44,7 @@ types:
|
||||
traceId:
|
||||
type: optional<string>
|
||||
docs: traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId.
|
||||
PaginatedDatasetRunItems:
|
||||
properties:
|
||||
data: list<commons.DatasetRunItem>
|
||||
meta: pagination.MetaResponse
|
||||
|
||||
@@ -6,71 +6,64 @@ service:
|
||||
auth: true
|
||||
base-path: /api/public
|
||||
endpoints:
|
||||
daily:
|
||||
docs: Get daily metrics of the Langfuse project
|
||||
metrics:
|
||||
docs: Get metrics from the Langfuse project using a query object
|
||||
method: GET
|
||||
path: /metrics/daily
|
||||
path: /metrics
|
||||
request:
|
||||
name: GetDailyMetricsRequest
|
||||
name: GetMetricsRequest
|
||||
query-parameters:
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
traceName:
|
||||
type: optional<string>
|
||||
docs: Optional filter by the name of the trace
|
||||
userId:
|
||||
type: optional<string>
|
||||
docs: Optional filter by the userId associated with the trace
|
||||
tags:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for metrics where traces include all of these tags
|
||||
environment:
|
||||
type: optional<string>
|
||||
allow-multiple: true
|
||||
docs: Optional filter for metrics where events include any of these environments
|
||||
fromTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Optional filter to only include traces and observations on or after a certain datetime (ISO 8601)
|
||||
toTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Optional filter to only include traces and observations before a certain datetime (ISO 8601)
|
||||
response: DailyMetrics
|
||||
query:
|
||||
type: string
|
||||
docs: |
|
||||
JSON string containing the query parameters with the following structure:
|
||||
```json
|
||||
{
|
||||
"view": string, // Required. One of "traces", "observations", "scores-numeric", "scores-categorical"
|
||||
"dimensions": [ // Optional. Default: []
|
||||
{
|
||||
"field": string // Field to group by, e.g. "name", "userId", "sessionId"
|
||||
}
|
||||
],
|
||||
"metrics": [ // Required. At least one metric must be provided
|
||||
{
|
||||
"measure": string, // What to measure, e.g. "count", "latency", "value"
|
||||
"aggregation": string // How to aggregate, e.g. "count", "sum", "avg", "p95", "histogram"
|
||||
}
|
||||
],
|
||||
"filters": [ // Optional. Default: []
|
||||
{
|
||||
"column": string, // Column to filter on
|
||||
"operator": string, // Operator, e.g. "=", ">", "<", "contains"
|
||||
"value": any, // Value to compare against
|
||||
"type": string, // Data type, e.g. "string", "number", "stringObject"
|
||||
"key": string // Required only when filtering on metadata
|
||||
}
|
||||
],
|
||||
"timeDimension": { // Optional. Default: null. If provided, results will be grouped by time
|
||||
"granularity": string // One of "minute", "hour", "day", "week", "month", "auto"
|
||||
},
|
||||
"fromTimestamp": string, // Required. ISO datetime string for start of time range
|
||||
"toTimestamp": string, // Required. ISO datetime string for end of time range
|
||||
"orderBy": [ // Optional. Default: null
|
||||
{
|
||||
"field": string, // Field to order by
|
||||
"direction": string // "asc" or "desc"
|
||||
}
|
||||
],
|
||||
"config": { // Optional. Query-specific configuration
|
||||
"bins": number, // Optional. Number of bins for histogram (1-100), default: 10
|
||||
"row_limit": number // Optional. Row limit for results (1-1000)
|
||||
}
|
||||
}
|
||||
```
|
||||
response: MetricsResponse
|
||||
types:
|
||||
DailyMetrics:
|
||||
MetricsResponse:
|
||||
properties:
|
||||
data:
|
||||
type: list<DailyMetricsDetails>
|
||||
docs: A list of daily metrics, only days with ingested data are included.
|
||||
meta: pagination.MetaResponse
|
||||
DailyMetricsDetails:
|
||||
properties:
|
||||
date: date
|
||||
countTraces: integer
|
||||
countObservations: integer
|
||||
totalCost:
|
||||
type: double
|
||||
docs: Total model cost in USD
|
||||
usage: list<UsageByModel>
|
||||
UsageByModel:
|
||||
docs: Daily usage of a given model. Usage corresponds to the unit set for the specific model (e.g. tokens).
|
||||
properties:
|
||||
model: optional<string>
|
||||
inputUsage:
|
||||
type: integer
|
||||
docs: Total number of generation input units (e.g. tokens)
|
||||
outputUsage:
|
||||
type: integer
|
||||
docs: Total number of generation output units (e.g. tokens)
|
||||
totalUsage:
|
||||
type: integer
|
||||
docs: Total number of generation total units (e.g. tokens)
|
||||
countTraces: integer
|
||||
countObservations: integer
|
||||
totalCost:
|
||||
type: double
|
||||
docs: Total model cost in USD
|
||||
type: list<map<string, unknown>>
|
||||
docs: |
|
||||
The metrics data. Each item in the list contains the metric values and dimensions requested in the query.
|
||||
Format varies based on the query parameters.
|
||||
Histograms will return an array with [lower, upper, height] tuples.
|
||||
|
||||
@@ -39,7 +39,7 @@ service:
|
||||
docs: Optional filter for observations where the environment is one of the provided values.
|
||||
fromStartTime:
|
||||
type: optional<datetime>
|
||||
docs: Retrieve only observations with a start_time or or after this datetime (ISO 8601).
|
||||
docs: Retrieve only observations with a start_time on or after this datetime (ISO 8601).
|
||||
toStartTime:
|
||||
type: optional<datetime>
|
||||
docs: Retrieve only observations with a start_time before this datetime (ISO 8601).
|
||||
|
||||
+19
-1
@@ -37,6 +37,12 @@ service:
|
||||
request: MembershipRequest
|
||||
response: MembershipResponse
|
||||
|
||||
getOrganizationProjects:
|
||||
docs: Get all projects for the organization associated with the API key (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: /organizations/projects
|
||||
response: OrganizationProjectsResponse
|
||||
|
||||
types:
|
||||
MembershipRole:
|
||||
enum:
|
||||
@@ -59,4 +65,16 @@ types:
|
||||
|
||||
MembershipsResponse:
|
||||
properties:
|
||||
memberships: list<MembershipResponse>
|
||||
memberships: list<MembershipResponse>
|
||||
|
||||
OrganizationProject:
|
||||
properties:
|
||||
id: string
|
||||
name: string
|
||||
metadata: optional<map<string, unknown>>
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
|
||||
OrganizationProjectsResponse:
|
||||
properties:
|
||||
projects: list<OrganizationProject>
|
||||
@@ -11,7 +11,7 @@ service:
|
||||
method: GET
|
||||
path: /projects
|
||||
response: Projects
|
||||
|
||||
|
||||
create:
|
||||
docs: Create a new project (requires organization-scoped API key)
|
||||
method: POST
|
||||
@@ -26,9 +26,9 @@ service:
|
||||
docs: Optional metadata for the project
|
||||
retention:
|
||||
type: integer
|
||||
docs: Number of days to retain data. Must be 0 or at least 7 days. Requires data-retention entitlement for non-zero values. Optional.
|
||||
docs: Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional.
|
||||
response: Project
|
||||
|
||||
|
||||
update:
|
||||
docs: Update a project by ID (requires organization-scoped API key).
|
||||
method: PUT
|
||||
@@ -45,9 +45,9 @@ service:
|
||||
docs: Optional metadata for the project
|
||||
retention:
|
||||
type: integer
|
||||
docs: Number of days to retain data. Must be 0 or at least 7 days. Requires data-retention entitlement for non-zero values. Optional.
|
||||
docs: Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional.
|
||||
response: Project
|
||||
|
||||
|
||||
delete:
|
||||
docs: Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously.
|
||||
method: DELETE
|
||||
@@ -57,7 +57,7 @@ service:
|
||||
response:
|
||||
status-code: 202
|
||||
type: ProjectDeletionResponse
|
||||
|
||||
|
||||
# API Key endpoints
|
||||
getApiKeys:
|
||||
docs: Get all API keys for a project (requires organization-scoped API key)
|
||||
@@ -104,14 +104,14 @@ types:
|
||||
type: map<string, unknown>
|
||||
docs: Metadata for the project
|
||||
retentionDays:
|
||||
type: integer
|
||||
type: optional<integer>
|
||||
docs: Number of days to retain data. Null or 0 means no retention. Omitted if no retention is configured.
|
||||
|
||||
ProjectDeletionResponse:
|
||||
properties:
|
||||
success: boolean
|
||||
message: string
|
||||
|
||||
|
||||
ApiKeyList:
|
||||
docs: List of API keys for a project
|
||||
properties:
|
||||
@@ -137,7 +137,7 @@ types:
|
||||
secretKey: string
|
||||
displaySecretKey: string
|
||||
note: optional<string>
|
||||
|
||||
|
||||
ApiKeyDeletionResponse:
|
||||
docs: Response for API key deletion
|
||||
properties:
|
||||
|
||||
+7
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langfuse",
|
||||
"version": "3.56.0",
|
||||
"version": "3.70.0",
|
||||
"author": "engineering@langfuse.com",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@@ -18,7 +18,7 @@
|
||||
"db:seed:examples": "turbo run db:seed:examples",
|
||||
"nuke": "bash ./scripts/nuke.sh",
|
||||
"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-f": "pnpm i && pnpm run infra:dev:prune && pnpm run infra:dev:up --pull always && pnpm --filter=shared run db:reset -f && SKIP_CONFIRM=1 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",
|
||||
@@ -31,12 +31,12 @@
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@release-it/bumper": "^7.0.1",
|
||||
"@release-it/bumper": "^7.0.5",
|
||||
"braces": "3.0.3",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"husky": "^9.0.11",
|
||||
"prettier": "^3.3.3",
|
||||
"release-it": "^18.1.2",
|
||||
"release-it": "^19.0.3",
|
||||
"turbo": "^1.13.4"
|
||||
},
|
||||
"release-it": {
|
||||
@@ -86,7 +86,9 @@
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"nanoid": "^3.3.8",
|
||||
"katex": "^0.16.21"
|
||||
"katex": "^0.16.21",
|
||||
"tar-fs": "^2.1.2",
|
||||
"rollup@^4.0.0": "^4.22.4"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"next-auth@4.24.11": "patches/next-auth@4.24.11.patch"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP VIEW IF EXISTS analytics_traces ON CLUSTER default;
|
||||
@@ -0,0 +1,15 @@
|
||||
CREATE VIEW analytics_traces ON CLUSTER default AS
|
||||
SELECT
|
||||
project_id,
|
||||
toStartOfHour(timestamp) AS hour,
|
||||
uniq(id) AS countTraces,
|
||||
max(user_id IS NOT NULL) AS hasUsers,
|
||||
max(session_id IS NOT NULL) AS hasSessions,
|
||||
max(if(environment != 'default', 1, 0)) AS hasEnvironments,
|
||||
max(length(tags) > 0) AS hasTags
|
||||
FROM
|
||||
traces
|
||||
WHERE toStartOfHour(timestamp) <= toStartOfHour(subtractHours(now(), 1))
|
||||
GROUP BY
|
||||
project_id,
|
||||
hour;
|
||||
@@ -0,0 +1 @@
|
||||
DROP VIEW IF EXISTS analytics_observations ON CLUSTER default;
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE VIEW analytics_observations ON CLUSTER default AS
|
||||
SELECT
|
||||
project_id,
|
||||
type,
|
||||
toStartOfHour(start_time) AS hour,
|
||||
uniq(id) AS countObservations,
|
||||
max(level != 'DEFAULT') AS hasLevel,
|
||||
max(provided_model_name IS NOT NULL) AS hasProvidedModelName,
|
||||
max(length(provided_usage_details) > 0) AS hasProvidedUsageDetails,
|
||||
max(length(provided_cost_details) > 0) AS hasProvidedCostDetails,
|
||||
max(prompt_name IS NOT NULL) AS hasPromptName
|
||||
FROM
|
||||
observations
|
||||
WHERE toStartOfHour(start_time) <= toStartOfHour(subtractHours(now(), 1))
|
||||
GROUP BY
|
||||
project_id,
|
||||
type,
|
||||
hour;
|
||||
@@ -0,0 +1 @@
|
||||
DROP VIEW IF EXISTS analytics_scores ON CLUSTER default;
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE VIEW analytics_scores ON CLUSTER default AS
|
||||
SELECT
|
||||
project_id,
|
||||
toStartOfHour(timestamp) AS hour,
|
||||
uniq(id) AS countScores,
|
||||
max(source = 'ANNOTATION') AS hasAnnotation,
|
||||
max(source = 'API') AS hasApi,
|
||||
max(source = 'EVAL') AS hasEval,
|
||||
max(observation_id IS NOT NULL) AS hasObservationScore,
|
||||
max(session_id IS NOT NULL) AS hasSessionScore,
|
||||
max(dataset_run_id IS NOT NULL) AS hasDatasetRunScore,
|
||||
max(data_type = 'BOOLEAN') AS hasBoolScore,
|
||||
max(data_type = 'NUMERIC') AS hasNumericScore,
|
||||
max(data_type = 'CATEGORICAL') AS hasCategoricalScore,
|
||||
max(comment IS NOT NULL) AS hasComment
|
||||
FROM
|
||||
scores
|
||||
WHERE toStartOfHour(timestamp) <= toStartOfHour(subtractHours(now(), 1))
|
||||
GROUP BY
|
||||
project_id,
|
||||
hour;
|
||||
@@ -0,0 +1 @@
|
||||
DROP VIEW IF EXISTS analytics_traces;
|
||||
@@ -0,0 +1,15 @@
|
||||
CREATE VIEW analytics_traces AS
|
||||
SELECT
|
||||
project_id,
|
||||
toStartOfHour(timestamp) AS hour,
|
||||
uniq(id) AS countTraces,
|
||||
max(user_id IS NOT NULL) AS hasUsers,
|
||||
max(session_id IS NOT NULL) AS hasSessions,
|
||||
max(if(environment != 'default', 1, 0)) AS hasEnvironments,
|
||||
max(length(tags) > 0) AS hasTags
|
||||
FROM
|
||||
traces
|
||||
WHERE toStartOfHour(timestamp) <= toStartOfHour(subtractHours(now(), 1))
|
||||
GROUP BY
|
||||
project_id,
|
||||
hour;
|
||||
@@ -0,0 +1 @@
|
||||
DROP VIEW IF EXISTS analytics_observations;
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE VIEW analytics_observations AS
|
||||
SELECT
|
||||
project_id,
|
||||
type,
|
||||
toStartOfHour(start_time) AS hour,
|
||||
uniq(id) AS countObservations,
|
||||
max(level != 'DEFAULT') AS hasLevel,
|
||||
max(provided_model_name IS NOT NULL) AS hasProvidedModelName,
|
||||
max(length(provided_usage_details) > 0) AS hasProvidedUsageDetails,
|
||||
max(length(provided_cost_details) > 0) AS hasProvidedCostDetails,
|
||||
max(prompt_name IS NOT NULL) AS hasPromptName
|
||||
FROM
|
||||
observations
|
||||
WHERE toStartOfHour(start_time) <= toStartOfHour(subtractHours(now(), 1))
|
||||
GROUP BY
|
||||
project_id,
|
||||
type,
|
||||
hour;
|
||||
@@ -0,0 +1 @@
|
||||
DROP VIEW IF EXISTS analytics_scores;
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE VIEW analytics_scores AS
|
||||
SELECT
|
||||
project_id,
|
||||
toStartOfHour(timestamp) AS hour,
|
||||
uniq(id) AS countScores,
|
||||
max(source = 'ANNOTATION') AS hasAnnotation,
|
||||
max(source = 'API') AS hasApi,
|
||||
max(source = 'EVAL') AS hasEval,
|
||||
max(observation_id IS NOT NULL) AS hasObservationScore,
|
||||
max(session_id IS NOT NULL) AS hasSessionScore,
|
||||
max(dataset_run_id IS NOT NULL) AS hasDatasetRunScore,
|
||||
max(data_type = 'BOOLEAN') AS hasBoolScore,
|
||||
max(data_type = 'NUMERIC') AS hasNumericScore,
|
||||
max(data_type = 'CATEGORICAL') AS hasCategoricalScore,
|
||||
max(comment IS NOT NULL) AS hasComment
|
||||
FROM
|
||||
scores
|
||||
WHERE toStartOfHour(timestamp) <= toStartOfHour(subtractHours(now(), 1))
|
||||
GROUP BY
|
||||
project_id,
|
||||
hour;
|
||||
@@ -36,8 +36,12 @@ if [ "$CLICKHOUSE_CLUSTER_ENABLED" == "false" ] ; then
|
||||
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=${CLICKHOUSE_DB}&x-multi-statement=true&x-migrations-table-engine=MergeTree"
|
||||
fi
|
||||
|
||||
# Execute the up command
|
||||
migrate -source file://clickhouse/migrations/unclustered -database "$DATABASE_URL" down
|
||||
# If SKIP_CONFIRM is set, automatically answer the confirmation prompt. Otherwise run interactively.
|
||||
if [ "$SKIP_CONFIRM" = "1" ] || [ "$SKIP_CONFIRM" = "true" ]; then
|
||||
printf 'y\n' | migrate -source file://clickhouse/migrations/unclustered -database "$DATABASE_URL" down
|
||||
else
|
||||
migrate -source file://clickhouse/migrations/unclustered -database "$DATABASE_URL" down
|
||||
fi
|
||||
else
|
||||
if [ "$CLICKHOUSE_MIGRATION_SSL" = true ] ; then
|
||||
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=${CLICKHOUSE_DB}&x-multi-statement=true&secure=true&skip_verify=true&x-cluster-name=${CLICKHOUSE_CLUSTER_NAME}&x-migrations-table-engine=ReplicatedMergeTree"
|
||||
@@ -45,6 +49,10 @@ else
|
||||
DATABASE_URL="${CLICKHOUSE_MIGRATION_URL}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=${CLICKHOUSE_DB}&x-multi-statement=true&x-cluster-name=${CLICKHOUSE_CLUSTER_NAME}&x-migrations-table-engine=ReplicatedMergeTree"
|
||||
fi
|
||||
|
||||
# Execute the up command
|
||||
migrate -source file://clickhouse/migrations/clustered -database "$DATABASE_URL" down
|
||||
# If SKIP_CONFIRM is set, automatically answer the confirmation prompt. Otherwise run interactively.
|
||||
if [ "$SKIP_CONFIRM" = "1" ] || [ "$SKIP_CONFIRM" = "true" ]; then
|
||||
printf 'y\n' | migrate -source file://clickhouse/migrations/clustered -database "$DATABASE_URL" down
|
||||
else
|
||||
migrate -source file://clickhouse/migrations/clustered -database "$DATABASE_URL" down
|
||||
fi
|
||||
fi
|
||||
@@ -33,7 +33,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 65",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 70",
|
||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"db:migrate": "DISABLE_ERD=false dotenv -e ../../.env -- npx prisma migrate dev",
|
||||
"db:push": "DISABLE_ERD=false dotenv -e ../../.env -- npx prisma db push",
|
||||
@@ -60,18 +60,18 @@
|
||||
"@aws-sdk/lib-storage": "^3.675.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.679.0",
|
||||
"@azure/storage-blob": "^12.26.0",
|
||||
"@clickhouse/client": "^1.4.0",
|
||||
"@clickhouse/client": "^1.11.2",
|
||||
"@google-cloud/storage": "^7.15.2",
|
||||
"@langchain/anthropic": "^0.3.12",
|
||||
"@langchain/aws": "^0.1.3",
|
||||
"@langchain/core": "^0.3.37",
|
||||
"@langchain/google-genai": "^0.1.9",
|
||||
"@langchain/google-vertexai": "^0.1.8",
|
||||
"@langchain/openai": "^0.3.17",
|
||||
"@langchain/anthropic": "^0.3.22",
|
||||
"@langchain/aws": "^0.1.11",
|
||||
"@langchain/core": "^0.3.58",
|
||||
"@langchain/google-genai": "^0.2.12",
|
||||
"@langchain/google-vertexai": "^0.2.12",
|
||||
"@langchain/openai": "^0.5.13",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"@prisma/client": "^6.3.0",
|
||||
"@react-email/components": "^0.0.19",
|
||||
"@react-email/render": "^0.0.15",
|
||||
"@react-email/components": "^0.0.42",
|
||||
"@react-email/render": "^1.1.2",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"axios": "^1.8.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -82,8 +82,8 @@
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"ioredis": "^5.4.1",
|
||||
"kysely": "^0.27.4",
|
||||
"langchain": "^0.3.15",
|
||||
"langfuse-langchain": "3.30.3",
|
||||
"langchain": "^0.3.28",
|
||||
"langfuse-langchain": "3.37.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lossless-json": "^4.0.2",
|
||||
"next-auth": "^4.24.11",
|
||||
@@ -91,7 +91,7 @@
|
||||
"prisma-extension-kysely": "^2.1.0",
|
||||
"uuid": "^9.0.1",
|
||||
"winston": "^3.15.0",
|
||||
"zod": "^3.23.8",
|
||||
"zod": "^3.25.62",
|
||||
"zod-to-json-schema": "^3.23.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -101,6 +101,7 @@
|
||||
"@types/node": "^20.11.29",
|
||||
"@types/nodemailer": "^6.4.16",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/react": "18.2.79",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/parser": "^7.12.0",
|
||||
"eslint": "^8.57.0",
|
||||
|
||||
@@ -109,7 +109,9 @@ export const DashboardWidgetChartType = {
|
||||
BAR_TIME_SERIES: "BAR_TIME_SERIES",
|
||||
HORIZONTAL_BAR: "HORIZONTAL_BAR",
|
||||
VERTICAL_BAR: "VERTICAL_BAR",
|
||||
PIE: "PIE"
|
||||
PIE: "PIE",
|
||||
NUMBER: "NUMBER",
|
||||
HISTOGRAM: "HISTOGRAM"
|
||||
} as const;
|
||||
export type DashboardWidgetChartType = (typeof DashboardWidgetChartType)[keyof typeof DashboardWidgetChartType];
|
||||
export type Account = {
|
||||
@@ -227,8 +229,8 @@ export type BlobStorageIntegration = {
|
||||
type: BlobStorageIntegrationType;
|
||||
bucket_name: string;
|
||||
prefix: string;
|
||||
access_key_id: string;
|
||||
secret_access_key: string;
|
||||
access_key_id: string | null;
|
||||
secret_access_key: string | null;
|
||||
region: string;
|
||||
endpoint: string | null;
|
||||
force_path_style: boolean;
|
||||
@@ -325,17 +327,29 @@ export type DatasetRuns = {
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type EvalTemplate = {
|
||||
export type DefaultLlmModel = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
project_id: string;
|
||||
llm_api_key_id: string;
|
||||
provider: string;
|
||||
adapter: string;
|
||||
model: string;
|
||||
model_params: unknown | null;
|
||||
};
|
||||
export type EvalTemplate = {
|
||||
id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
project_id: string | null;
|
||||
name: string;
|
||||
version: number;
|
||||
prompt: string;
|
||||
model: string;
|
||||
provider: string;
|
||||
model_params: unknown;
|
||||
partner: string | null;
|
||||
model: string | null;
|
||||
provider: string | null;
|
||||
model_params: unknown | null;
|
||||
vars: Generated<string[]>;
|
||||
output_schema: unknown;
|
||||
};
|
||||
@@ -361,6 +375,7 @@ export type JobExecution = {
|
||||
updated_at: Generated<Timestamp>;
|
||||
project_id: string;
|
||||
job_configuration_id: string;
|
||||
job_template_id: string | null;
|
||||
status: JobExecutionStatus;
|
||||
start_time: Timestamp | null;
|
||||
end_time: Timestamp | null;
|
||||
@@ -702,6 +717,7 @@ export type DB = {
|
||||
dataset_run_items: DatasetRunItems;
|
||||
dataset_runs: DatasetRuns;
|
||||
datasets: Dataset;
|
||||
default_llm_models: DefaultLlmModel;
|
||||
eval_templates: EvalTemplate;
|
||||
job_configurations: JobConfiguration;
|
||||
job_executions: JobExecution;
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS "trace_media_project_id_media_id_idx" ON "trace_media"("project_id", "media_id");
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS "observation_media_project_id_media_id_idx" ON "observation_media"("project_id", "media_id");
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- Index cannot be created concurrently in a transaction, so we need to create it separately
|
||||
-- Prerequisite for migration 20250518075613_media_relax_id_uniqueness_to_project_only
|
||||
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS "media_project_id_id_key" ON "media"("project_id", "id");
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `media` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- A unique constraint covering the columns `[project_id,id]` on the table `media` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "observation_media" DROP CONSTRAINT "observation_media_media_id_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "trace_media" DROP CONSTRAINT "trace_media_media_id_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "media" DROP CONSTRAINT "media_pkey";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "trace_media" ADD CONSTRAINT "trace_media_media_id_project_id_fkey" FOREIGN KEY ("media_id", "project_id") REFERENCES "media"("id", "project_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "observation_media" ADD CONSTRAINT "observation_media_media_id_project_id_fkey" FOREIGN KEY ("media_id", "project_id") REFERENCES "media"("id", "project_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/* Triple y and y_size in dashboards.definition->'widgets' */
|
||||
WITH updated AS (
|
||||
SELECT
|
||||
id,
|
||||
CASE
|
||||
WHEN jsonb_array_length(definition->'widgets') = 0 THEN definition
|
||||
ELSE jsonb_set(
|
||||
definition, -- original JSON
|
||||
'{widgets}', -- path to replace
|
||||
(
|
||||
SELECT jsonb_agg( -- rebuild widgets array
|
||||
/* 1) y * 3 2) y_size * 3 */
|
||||
jsonb_set(
|
||||
jsonb_set(widget, '{y}',
|
||||
to_jsonb( ((widget->>'y')::int * 3) ), /* y */
|
||||
true
|
||||
),
|
||||
'{y_size}',
|
||||
to_jsonb( ((widget->>'y_size')::int * 3) ),/* y_size */
|
||||
true
|
||||
)
|
||||
)
|
||||
FROM jsonb_array_elements(definition->'widgets') AS widget
|
||||
),
|
||||
true -- create path if absent
|
||||
)
|
||||
END AS new_def
|
||||
FROM dashboards
|
||||
)
|
||||
UPDATE dashboards d
|
||||
SET definition = u.new_def
|
||||
FROM updated u
|
||||
WHERE d.id = u.id;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "DashboardWidgetChartType" ADD VALUE 'NUMBER';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- DropIndex
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "observation_media_project_id_observation_id_idx";
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "default_llm_models" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"llm_api_key_id" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"adapter" TEXT NOT NULL,
|
||||
"model" TEXT NOT NULL,
|
||||
"model_params" JSONB,
|
||||
|
||||
CONSTRAINT "default_llm_models_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "default_llm_models" ADD CONSTRAINT "default_llm_models_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "default_llm_models" ADD CONSTRAINT "default_llm_models_llm_api_key_id_fkey" FOREIGN KEY ("llm_api_key_id") REFERENCES "llm_api_keys"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddUniqueConstraint
|
||||
ALTER TABLE "default_llm_models" ADD CONSTRAINT "default_llm_models_project_id_key" UNIQUE ("project_id");
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "eval_templates" ALTER COLUMN "project_id" DROP NOT NULL;
|
||||
ALTER TABLE "eval_templates" ALTER COLUMN "model" DROP NOT NULL;
|
||||
ALTER TABLE "eval_templates" ALTER COLUMN "provider" DROP NOT NULL;
|
||||
ALTER TABLE "eval_templates" ALTER COLUMN "model_params" DROP NOT NULL;
|
||||
ALTER TABLE "eval_templates" ADD COLUMN "partner" TEXT;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "job_executions" ADD COLUMN "job_template_id" TEXT;
|
||||
ALTER TABLE "job_executions" ADD CONSTRAINT "job_executions_job_template_id_fkey" FOREIGN KEY ("job_template_id") REFERENCES "eval_templates"("id") ON DELETE SET NULL;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "blob_storage_integrations" ALTER COLUMN "access_key_id" DROP NOT NULL,
|
||||
ALTER COLUMN "secret_access_key" DROP NOT NULL;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "DashboardWidgetChartType" ADD VALUE 'HISTOGRAM';
|
||||
@@ -157,6 +157,7 @@ model Project {
|
||||
Dashboard Dashboard[]
|
||||
DashboardWidget DashboardWidget[]
|
||||
TableViewPreset TableViewPreset[]
|
||||
DefaultLlmModel DefaultLlmModel[]
|
||||
|
||||
@@index([orgId])
|
||||
@@map("projects")
|
||||
@@ -222,8 +223,9 @@ model LlmApiKeys {
|
||||
extraHeaderKeys String[] @default([]) @map("extra_header_keys")
|
||||
config Json?
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
DefaultLlmModel DefaultLlmModel[] @relation("LlmApiKeyId")
|
||||
|
||||
@@unique([projectId, provider])
|
||||
@@map("llm_api_keys")
|
||||
@@ -793,18 +795,22 @@ model EvalTemplate {
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String? @map("project_id")
|
||||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
name String
|
||||
version Int
|
||||
prompt String
|
||||
model String
|
||||
provider String
|
||||
modelParams Json @map("model_params")
|
||||
name String
|
||||
version Int
|
||||
prompt String
|
||||
|
||||
partner String? // e.g. "ragas", describes the partner that created the template
|
||||
|
||||
model String?
|
||||
provider String?
|
||||
modelParams Json? @map("model_params")
|
||||
vars String[] @default([])
|
||||
outputSchema Json @map("output_schema")
|
||||
JobConfiguration JobConfiguration[]
|
||||
JobExecution JobExecution[]
|
||||
|
||||
@@unique([projectId, name, version])
|
||||
@@index([projectId, id])
|
||||
@@ -864,6 +870,9 @@ model JobExecution {
|
||||
jobConfigurationId String @map("job_configuration_id")
|
||||
jobConfiguration JobConfiguration @relation(fields: [jobConfigurationId], references: [id], onDelete: Cascade)
|
||||
|
||||
jobTemplateId String? @map("job_template_id")
|
||||
jobTemplate EvalTemplate? @relation(fields: [jobTemplateId], references: [id], onDelete: SetNull)
|
||||
|
||||
status JobExecutionStatus
|
||||
startTime DateTime? @map("start_time")
|
||||
endTime DateTime? @map("end_time")
|
||||
@@ -887,6 +896,26 @@ model JobExecution {
|
||||
@@map("job_executions")
|
||||
}
|
||||
|
||||
model DefaultLlmModel {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
projectId String @map("project_id")
|
||||
Project Project @relation(fields: [projectId], references: [id])
|
||||
|
||||
llmApiKeyId String @map("llm_api_key_id")
|
||||
LlmApiKey LlmApiKeys @relation("LlmApiKeyId", fields: [llmApiKeyId], references: [id], onDelete: Cascade)
|
||||
|
||||
provider String
|
||||
adapter String // This controls the interface that is used to connect with the LLM, e.g. 'openai' or 'anthropic'
|
||||
model String
|
||||
modelParams Json? @map("model_params")
|
||||
|
||||
@@unique([projectId])
|
||||
@@map("default_llm_models")
|
||||
}
|
||||
|
||||
// Single Sign-On configuration for a domain
|
||||
// This feature is part of the Enterprise Edition
|
||||
model SsoConfig {
|
||||
@@ -922,8 +951,8 @@ model BlobStorageIntegration {
|
||||
type BlobStorageIntegrationType @map("type")
|
||||
bucketName String @map("bucket_name")
|
||||
prefix String @map("prefix")
|
||||
accessKeyId String @map("access_key_id")
|
||||
secretAccessKey String @map("secret_access_key")
|
||||
accessKeyId String? @map("access_key_id")
|
||||
secretAccessKey String? @map("secret_access_key")
|
||||
region String @map("region")
|
||||
endpoint String? @map("endpoint")
|
||||
forcePathStyle Boolean @map("force_path_style")
|
||||
@@ -982,7 +1011,7 @@ model BatchExport {
|
||||
}
|
||||
|
||||
model Media {
|
||||
id String @id @default(cuid())
|
||||
id String
|
||||
sha256Hash String @map("sha_256_hash") @db.Char(44)
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
@@ -998,6 +1027,7 @@ model Media {
|
||||
TraceMedia TraceMedia[]
|
||||
ObservationMedia ObservationMedia[]
|
||||
|
||||
@@unique([projectId, id])
|
||||
@@unique([projectId, sha256Hash])
|
||||
@@map("media")
|
||||
}
|
||||
@@ -1009,11 +1039,12 @@ model TraceMedia {
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
mediaId String @map("media_id")
|
||||
media Media @relation(fields: [mediaId], references: [id], onDelete: Cascade)
|
||||
media Media @relation(fields: [mediaId, projectId], references: [id, projectId], onDelete: Cascade)
|
||||
traceId String @map("trace_id")
|
||||
field String @map("field")
|
||||
|
||||
@@unique([projectId, traceId, mediaId, field])
|
||||
@@index([projectId, mediaId])
|
||||
@@map("trace_media")
|
||||
}
|
||||
|
||||
@@ -1024,13 +1055,13 @@ model ObservationMedia {
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
mediaId String @map("media_id")
|
||||
media Media @relation(fields: [mediaId], references: [id], onDelete: Cascade)
|
||||
media Media @relation(fields: [mediaId, projectId], references: [id, projectId], onDelete: Cascade)
|
||||
traceId String @map("trace_id")
|
||||
observationId String @map("observation_id")
|
||||
field String @map("field")
|
||||
|
||||
@@unique([projectId, traceId, observationId, mediaId, field])
|
||||
@@index([projectId, observationId])
|
||||
@@index([projectId, mediaId])
|
||||
@@map("observation_media")
|
||||
}
|
||||
|
||||
@@ -1124,6 +1155,8 @@ enum DashboardWidgetChartType {
|
||||
HORIZONTAL_BAR
|
||||
VERTICAL_BAR
|
||||
PIE
|
||||
NUMBER
|
||||
HISTOGRAM
|
||||
}
|
||||
|
||||
model DashboardWidget {
|
||||
|
||||
@@ -496,7 +496,7 @@ async function createDashboardsAndWidgets(projects: Project[]) {
|
||||
x: 0,
|
||||
y: 0,
|
||||
x_size: 6,
|
||||
y_size: 2,
|
||||
y_size: 6,
|
||||
},
|
||||
{
|
||||
type: "widget",
|
||||
@@ -505,7 +505,7 @@ async function createDashboardsAndWidgets(projects: Project[]) {
|
||||
x: 6,
|
||||
y: 0,
|
||||
x_size: 6,
|
||||
y_size: 2,
|
||||
y_size: 6,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { jsonSchema } from "../utils/zod";
|
||||
import { MetadataDomain } from "./traces";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ScoreDataType } from "@prisma/client";
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { MetadataDomain } from "./traces";
|
||||
|
||||
export const ScoreSource = {
|
||||
@@ -31,7 +31,7 @@ export const ScoreSchema = z.object({
|
||||
queueId: z.string().nullable(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
dataType: z.nativeEnum(ScoreDataType),
|
||||
dataType: z.enum(ScoreDataType),
|
||||
});
|
||||
|
||||
export type ScoreDomain = z.infer<typeof ScoreSchema>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { singleFilter } from "../interfaces/filters";
|
||||
import { orderBy } from "../interfaces/orderBy";
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
|
||||
export enum TableViewPresetTableName {
|
||||
Traces = "traces",
|
||||
@@ -17,7 +17,7 @@ const TableViewPresetDomainSchema = z.object({
|
||||
updatedAt: z.date(),
|
||||
createdBy: z.string().nullable(),
|
||||
name: z.string(),
|
||||
tableName: z.nativeEnum(TableViewPresetTableName),
|
||||
tableName: z.enum(TableViewPresetTableName),
|
||||
filters: z.array(singleFilter),
|
||||
columnOrder: z.array(z.string()),
|
||||
columnVisibility: z.record(z.string(), z.boolean()),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { jsonSchema, jsonSchemaNullable } from "../utils/zod";
|
||||
|
||||
export const MetadataDomain = z.record(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import { removeEmptyEnvVariables } from "./utils/environment";
|
||||
|
||||
const EnvSchema = z.object({
|
||||
@@ -9,10 +9,7 @@ const EnvSchema = z.object({
|
||||
NEXTAUTH_URL: z.string().url().optional(),
|
||||
REDIS_HOST: z.string().nullish(),
|
||||
REDIS_PORT: z.coerce
|
||||
.number({
|
||||
description:
|
||||
".env files convert numbers to strings, therefore we have to enforce them to be numbers",
|
||||
})
|
||||
.number() // .env files convert numbers to strings, therefore we have to enforce them to be numbers
|
||||
.positive()
|
||||
.max(65536, `options.port should be >= 0 and < 65536`)
|
||||
.default(6379)
|
||||
@@ -24,6 +21,9 @@ const EnvSchema = z.object({
|
||||
REDIS_TLS_CERT_PATH: z.string().optional(),
|
||||
REDIS_TLS_KEY_PATH: z.string().optional(),
|
||||
REDIS_ENABLE_AUTO_PIPELINING: z.enum(["true", "false"]).default("true"),
|
||||
// Redis Cluster Configuration
|
||||
REDIS_CLUSTER_ENABLED: z.enum(["true", "false"]).default("false"),
|
||||
REDIS_CLUSTER_NODES: z.string().optional(),
|
||||
ENCRYPTION_KEY: z
|
||||
.string()
|
||||
.length(
|
||||
@@ -38,23 +38,33 @@ const EnvSchema = z.object({
|
||||
CLICKHOUSE_DB: z.string().default("default"),
|
||||
CLICKHOUSE_USER: z.string(),
|
||||
CLICKHOUSE_PASSWORD: z.string(),
|
||||
CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL: z.coerce.number().int().default(9000),
|
||||
CLICKHOUSE_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(25),
|
||||
|
||||
LANGFUSE_INGESTION_QUEUE_DELAY_MS: z.coerce
|
||||
.number()
|
||||
.nonnegative()
|
||||
.default(15_000),
|
||||
LANGFUSE_INGESTION_QUEUE_SHARD_COUNT: z.coerce
|
||||
.number()
|
||||
.positive()
|
||||
.default(1),
|
||||
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"),
|
||||
LANGFUSE_LOG_PROPAGATED_HEADERS: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) =>
|
||||
s ? s.split(",").map((s) => s.toLowerCase().trim()) : [],
|
||||
),
|
||||
ENABLE_AWS_CLOUDWATCH_METRIC_PUBLISHING: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_S3_CONCURRENT_WRITES: z.coerce.number().positive().default(50),
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: z.string({
|
||||
required_error: "Langfuse requires a bucket name for S3 Event Uploads.",
|
||||
}),
|
||||
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: z.string(), // Langfuse requires a bucket name for S3 Event Uploads.
|
||||
LANGFUSE_S3_EVENT_UPLOAD_PREFIX: z.string().default(""),
|
||||
LANGFUSE_S3_EVENT_UPLOAD_REGION: z.string().optional(),
|
||||
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: z.string().optional(),
|
||||
@@ -63,6 +73,8 @@ const EnvSchema = z.object({
|
||||
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_S3_EVENT_UPLOAD_SSE: z.enum(["AES256", "aws:kms"]).optional(),
|
||||
LANGFUSE_S3_EVENT_UPLOAD_SSE_KMS_KEY_ID: z.string().optional(),
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: z.string().optional(),
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: z.string().default(""),
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_REGION: z.string().optional(),
|
||||
@@ -72,6 +84,8 @@ const EnvSchema = z.object({
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_SSE: z.enum(["AES256", "aws:kms"]).optional(),
|
||||
LANGFUSE_S3_MEDIA_UPLOAD_SSE_KMS_KEY_ID: z.string().optional(),
|
||||
LANGFUSE_USE_AZURE_BLOB: z.enum(["true", "false"]).default("false"),
|
||||
LANGFUSE_USE_GOOGLE_CLOUD_STORAGE: z.enum(["true", "false"]).default("false"),
|
||||
LANGFUSE_GOOGLE_CLOUD_STORAGE_CREDENTIALS: z.string().optional(),
|
||||
@@ -80,6 +94,8 @@ const EnvSchema = z.object({
|
||||
LANGFUSE_S3_CORE_DATA_EXPORT_IS_ENABLED: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_S3_CORE_DATA_EXPORT_SSE: z.enum(["AES256", "aws:kms"]).optional(),
|
||||
LANGFUSE_S3_CORE_DATA_EXPORT_SSE_KMS_KEY_ID: z.string().optional(),
|
||||
LANGFUSE_POSTGRES_METERING_DATA_EXPORT_IS_ENABLED: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
@@ -90,6 +106,7 @@ const EnvSchema = z.object({
|
||||
LANGFUSE_API_TRACE_OBSERVATIONS_SIZE_LIMIT_BYTES: z.coerce
|
||||
.number()
|
||||
.default(80e6), // 80MB
|
||||
LANGFUSE_CLICKHOUSE_DELETION_TIMEOUT_MS: z.coerce.number().default(240_000), // 4 minutes
|
||||
});
|
||||
|
||||
export const env: z.infer<typeof EnvSchema> =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { type ScoreDataType } from "../../db";
|
||||
|
||||
const NUMERIC: ScoreDataType = "NUMERIC";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { singleFilter } from "../../interfaces/filters";
|
||||
import { orderBy } from "../../interfaces/orderBy";
|
||||
import { BatchExportTableName } from "../batchExport/types";
|
||||
import { BatchTableNames } from "../../interfaces/tableNames";
|
||||
import { TracingSearchType } from "../../interfaces/search";
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum BatchActionType {
|
||||
@@ -20,6 +21,8 @@ export type ActionId = z.infer<typeof ActionIdSchema>;
|
||||
export const BatchActionQuerySchema = z.object({
|
||||
filter: z.array(singleFilter).nullable(),
|
||||
orderBy,
|
||||
searchQuery: z.string().optional(),
|
||||
searchType: z.array(TracingSearchType).optional(),
|
||||
});
|
||||
|
||||
export type BatchActionQuery = z.infer<typeof BatchActionQuerySchema>;
|
||||
@@ -29,11 +32,11 @@ export const CreateBatchActionSchema = z.object({
|
||||
actionId: ActionIdSchema,
|
||||
targetId: z.string().optional(),
|
||||
query: BatchActionQuerySchema,
|
||||
tableName: z.nativeEnum(BatchExportTableName),
|
||||
tableName: z.enum(BatchTableNames),
|
||||
});
|
||||
|
||||
export const GetIsBatchActionInProgressSchema = z.object({
|
||||
projectId: z.string(),
|
||||
actionId: ActionIdSchema,
|
||||
tableName: z.nativeEnum(BatchExportTableName),
|
||||
tableName: z.enum(BatchTableNames),
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
|
||||
import { BatchExport } from "@prisma/client";
|
||||
|
||||
import { singleFilter } from "../../interfaces/filters";
|
||||
import { orderBy } from "../../interfaces/orderBy";
|
||||
import { BatchTableNames } from "../../interfaces/tableNames";
|
||||
|
||||
export enum BatchExportStatus {
|
||||
QUEUED = "QUEUED",
|
||||
@@ -18,13 +19,9 @@ export enum BatchExportFileFormat {
|
||||
JSONL = "JSONL",
|
||||
}
|
||||
|
||||
export enum BatchExportTableName {
|
||||
Scores = "scores",
|
||||
Sessions = "sessions",
|
||||
Traces = "traces",
|
||||
Observations = "observations",
|
||||
DatasetRunItems = "dataset_run_items",
|
||||
}
|
||||
// Use shared BatchTableNames enum for consistency across batch operations
|
||||
// Keep BatchExportTableName as alias for backward compatibility
|
||||
export { BatchTableNames as BatchExportTableName };
|
||||
|
||||
export const exportOptions: Record<
|
||||
BatchExportFileFormat,
|
||||
@@ -44,7 +41,7 @@ export const exportOptions: Record<
|
||||
} as const;
|
||||
|
||||
export const BatchExportQuerySchema = z.object({
|
||||
tableName: z.nativeEnum(BatchExportTableName),
|
||||
tableName: z.enum(BatchTableNames),
|
||||
filter: z.array(singleFilter).nullable(),
|
||||
orderBy,
|
||||
limit: z.number().optional(),
|
||||
@@ -57,7 +54,7 @@ export const CreateBatchExportSchema = z.object({
|
||||
projectId: z.string(),
|
||||
name: z.string(),
|
||||
query: BatchExportQuerySchema,
|
||||
format: z.nativeEnum(BatchExportFileFormat),
|
||||
format: z.enum(BatchExportFileFormat),
|
||||
});
|
||||
|
||||
export const BatchExportSchema = z.object({
|
||||
@@ -69,9 +66,9 @@ export const BatchExportSchema = z.object({
|
||||
finishedAt: z.date().nullable(),
|
||||
expiresAt: z.date().nullable(),
|
||||
name: z.string(),
|
||||
status: z.nativeEnum(BatchExportStatus),
|
||||
status: z.enum(BatchExportStatus),
|
||||
query: BatchExportQuerySchema,
|
||||
format: z.nativeEnum(BatchExportFileFormat),
|
||||
format: z.enum(BatchExportFileFormat),
|
||||
url: z.string().nullable(),
|
||||
log: z.string().nullable(),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
const MAX_COMMENT_LENGTH = 3000;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
|
||||
export const langfuseObjects = [
|
||||
"trace",
|
||||
@@ -8,6 +8,9 @@ export const langfuseObjects = [
|
||||
"dataset_item",
|
||||
] as const;
|
||||
|
||||
const langfuseObject = z.enum(langfuseObjects);
|
||||
export type LangfuseEvaluationObject = z.infer<typeof langfuseObject>;
|
||||
|
||||
// variable mapping stored in the db for eval templates
|
||||
export const variableMapping = z
|
||||
.object({
|
||||
@@ -15,7 +18,7 @@ export const variableMapping = z
|
||||
// name of the observation to extract the variable from
|
||||
// not required for trace, as we only have one.
|
||||
objectName: z.string().nullish(),
|
||||
langfuseObject: z.enum(langfuseObjects),
|
||||
langfuseObject: langfuseObject,
|
||||
selectedColumnId: z.string(),
|
||||
jsonSelector: z.string().nullish(),
|
||||
})
|
||||
@@ -31,7 +34,7 @@ export const variableMappingList = z.array(variableMapping);
|
||||
export const wipVariableMapping = z.object({
|
||||
templateVariable: z.string(),
|
||||
objectName: z.string().nullish(),
|
||||
langfuseObject: z.enum(langfuseObjects),
|
||||
langfuseObject: langfuseObject,
|
||||
selectedColumnId: z.string().nullish(),
|
||||
jsonSelector: z.string().nullish(),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import z from "zod/v4";
|
||||
import { variableMapping } from "./types";
|
||||
|
||||
/**
|
||||
* Parses an unknown value to a string representation
|
||||
* This is used for any evaluation variable that needs string conversion
|
||||
*/
|
||||
export const parseUnknownToString = (value: unknown): string => {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (
|
||||
typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "boolean"
|
||||
) {
|
||||
return value.toString();
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (typeof value === "symbol") {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
return String(value);
|
||||
};
|
||||
|
||||
function parseJsonDefault(selectedColumn: unknown, jsonSelector: string) {
|
||||
// Front-end friendly JSON path extraction
|
||||
const parsedJson =
|
||||
typeof selectedColumn === "string"
|
||||
? JSON.parse(selectedColumn)
|
||||
: selectedColumn;
|
||||
|
||||
// Simple path extraction (could use a library)
|
||||
return jsonSelector
|
||||
.split(".")
|
||||
.reduce((o, key) => (o as any)?.[key], parsedJson);
|
||||
}
|
||||
|
||||
export function extractValueFromObject(
|
||||
obj: Record<string, unknown>,
|
||||
mapping: z.infer<typeof variableMapping>,
|
||||
parseJson?: (selectedColumn: unknown, jsonSelector: string) => unknown,
|
||||
): string {
|
||||
const selectedColumn = obj[mapping.selectedColumnId];
|
||||
const jsonParser = parseJson || parseJsonDefault;
|
||||
|
||||
let jsonSelectedColumn;
|
||||
if (mapping.jsonSelector && selectedColumn) {
|
||||
try {
|
||||
jsonSelectedColumn = jsonParser(selectedColumn, mapping.jsonSelector);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error parsing JSON selector: ${mapping.jsonSelector}`,
|
||||
error,
|
||||
);
|
||||
jsonSelectedColumn = selectedColumn;
|
||||
}
|
||||
} else {
|
||||
jsonSelectedColumn = selectedColumn;
|
||||
}
|
||||
|
||||
return parseUnknownToString(jsonSelectedColumn);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const PromptDependencyRegex = /@@@langfusePrompt:(.*?)@@@/g;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import { jsonSchema, publicApiPaginationZod } from "../../../../utils/zod";
|
||||
import { stringDateTime } from "../../../../utils/typeChecks";
|
||||
import { applyScoreValidation } from "../../../../utils/scores";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import { paginationMetaResponseZod } from "../../../../../utils/zod";
|
||||
import {
|
||||
DeleteScoreQuery,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import {
|
||||
CategoricalData,
|
||||
NumericData,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { GetScoreResponseDataV1 } from "./endpoints";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { paginationMetaResponseZod } from "../../../../../utils/zod";
|
||||
import { GetScoreQuery, GetScoresQuery } from "../shared";
|
||||
import { APIScoreSchemaV2 } from "./schemas";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import {
|
||||
CategoricalData,
|
||||
NumericData,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { GetScoreResponseDataV2 } from "./endpoints";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { APIScoreSchemaV2, APIScoreV2 } from "../api/v2/schemas";
|
||||
import { APIScoreSchemaV1, APIScoreV1 } from "../api/v1/schemas";
|
||||
import { ScoreDomain } from "../../../../domain";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { applyScoreValidation } from "../../../../utils/scores";
|
||||
import { PostScoreBodyFoundationSchema } from "../shared";
|
||||
import { isPresent } from "../../../../utils/typeChecks";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { jsonSchema } from "../../../utils/zod";
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { NonEmptyString } from "../../../utils/zod";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { ScoreConfig as ScoreConfigDbType } from "@prisma/client";
|
||||
|
||||
@@ -68,10 +68,10 @@ const CategoricalScoreConfig = z.object({
|
||||
const parseResult = Categories.safeParse(categories);
|
||||
if (!parseResult.success) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
code: "custom",
|
||||
message:
|
||||
"Category must be an array of objects with label value pairs, where labels and values are unique.",
|
||||
});
|
||||
} as z.core.$ZodIssueCustom);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ export const GetScoreConfigResponse = ValidatedScoreConfigSchema;
|
||||
|
||||
// POST /score-configs
|
||||
export const PostScoreConfigBody = z
|
||||
.union([
|
||||
.discriminatedUnion("dataType", [
|
||||
ScoreConfigPostBase.merge(CategoricalScoreConfig),
|
||||
ScoreConfigPostBase.merge(NumericScoreConfig),
|
||||
ScoreConfigPostBase.merge(
|
||||
|
||||
@@ -22,10 +22,11 @@ export * from "./server/llm/types";
|
||||
|
||||
// evals
|
||||
export * from "./features/evals/types";
|
||||
|
||||
export * from "./features/evals/utilities";
|
||||
// table actions
|
||||
export * from "./features/batchExport/types";
|
||||
export * from "./features/batchAction/types";
|
||||
export { BatchTableNames } from "./interfaces/tableNames";
|
||||
|
||||
// annotation
|
||||
export * from "./features/annotation/types";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import { CloudConfigRateLimit } from "./rate-limits";
|
||||
import { cloudConfigPlans } from "../features/entitlements/plans";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const BedrockConfigSchema = z.object({ region: z.string() });
|
||||
export type BedrockConfig = z.infer<typeof BedrockConfigSchema>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const filterOperators = {
|
||||
datetime: [">", "<", ">=", "<="],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const orderBy = z
|
||||
.object({
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { ApiAccessScope } from "../server";
|
||||
|
||||
export const RateLimitResource = z.enum([
|
||||
"ingestion",
|
||||
"public-api",
|
||||
"public-api-metrics",
|
||||
"public-api-daily-metrics-legacy",
|
||||
"prompts",
|
||||
"legacy-ingestion",
|
||||
"datasets",
|
||||
]);
|
||||
|
||||
// result of the rate limit check.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const TracingSearchType = z.enum(["id", "content"]);
|
||||
// id: for searching smaller columns like IDs, types, and other metadata
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Shared table names used across batch operations (exports, actions, etc.)
|
||||
* This enum provides a centralized definition of database table names
|
||||
* to avoid coupling between different batch operation types.
|
||||
*/
|
||||
export enum BatchTableNames {
|
||||
Scores = "scores",
|
||||
Sessions = "sessions",
|
||||
Traces = "traces",
|
||||
Observations = "observations",
|
||||
DatasetRunItems = "dataset_run_items",
|
||||
AuditLogs = "audit_logs",
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { Plan, plans } from "../../features/entitlements/plans";
|
||||
import { CloudConfigRateLimit } from "../../interfaces/rate-limits";
|
||||
import { ApiKeyScope } from "../../";
|
||||
@@ -55,4 +55,5 @@ export type ApiAccessScope = {
|
||||
plan: Plan;
|
||||
rateLimitOverrides: z.infer<typeof CloudConfigRateLimit>;
|
||||
apiKeyId: string;
|
||||
publicKey: string;
|
||||
};
|
||||
|
||||
@@ -45,9 +45,9 @@ export class ClickHouseClientManager {
|
||||
database: env.CLICKHOUSE_DB,
|
||||
http_headers: opts?.http_headers,
|
||||
settings: opts?.clickhouse_settings,
|
||||
request_timeout: opts?.request_timeout ?? 30000,
|
||||
// Include any other relevant config options
|
||||
};
|
||||
|
||||
return JSON.stringify(keyParams);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ export class ClickHouseClientManager {
|
||||
opts: NodeClickHouseClientConfigOptions,
|
||||
): ClickhouseClientType {
|
||||
const key = this.generateClientSettingsKey(opts);
|
||||
|
||||
if (!this.clientMap.has(key)) {
|
||||
const headers = opts?.http_headers ?? {};
|
||||
const activeSpan = getCurrentSpan();
|
||||
@@ -84,12 +83,25 @@ export class ClickHouseClientManager {
|
||||
password: env.CLICKHOUSE_PASSWORD,
|
||||
database: env.CLICKHOUSE_DB,
|
||||
http_headers: headers,
|
||||
keep_alive: {
|
||||
idle_socket_ttl: env.CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL,
|
||||
},
|
||||
max_open_connections: env.CLICKHOUSE_MAX_OPEN_CONNECTIONS,
|
||||
clickhouse_settings: {
|
||||
...cloudOptions,
|
||||
...opts.clickhouse_settings,
|
||||
async_insert: 1,
|
||||
wait_for_async_insert: 1, // if disabled, we won't get errors from clickhouse
|
||||
...(opts.request_timeout && opts.request_timeout > 30000
|
||||
? {
|
||||
send_progress_in_http_headers: 1,
|
||||
http_headers_progress_interval_ms: "25000", // UInt64, should be passed as a string
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(opts.request_timeout
|
||||
? { request_timeout: opts.request_timeout }
|
||||
: {}),
|
||||
});
|
||||
|
||||
this.clientMap.set(key, client);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client";
|
||||
import { ColumnDefinition, type TableNames } from "../tableDefinitions";
|
||||
import { FilterState } from "../types";
|
||||
import { filterOperators, timeFilter } from "../interfaces/filters";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
import { logger } from "./index";
|
||||
|
||||
const operatorReplacements = {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as opentelemetry from "@opentelemetry/api";
|
||||
import type { IncomingHttpHeaders } from "http";
|
||||
import { env } from "../env";
|
||||
|
||||
export type LangfuseContextProps = {
|
||||
headers?: IncomingHttpHeaders;
|
||||
userId?: string;
|
||||
projectId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new context containing baggage entries composed from
|
||||
* the supplied props (headers, userId, projectId). Existing baggage
|
||||
* entries are preserved.
|
||||
*/
|
||||
export const contextWithLangfuseProps = (
|
||||
props: LangfuseContextProps,
|
||||
): opentelemetry.Context => {
|
||||
const ctx = opentelemetry.context.active();
|
||||
let baggage =
|
||||
opentelemetry.propagation.getBaggage(ctx) ??
|
||||
opentelemetry.propagation.createBaggage();
|
||||
|
||||
if (props.headers) {
|
||||
(env.LANGFUSE_LOG_PROPAGATED_HEADERS as string[]).forEach((name) => {
|
||||
const value = props.headers![name];
|
||||
if (!value) return;
|
||||
const strValue = Array.isArray(value) ? JSON.stringify(value) : value;
|
||||
baggage = baggage.setEntry(`langfuse.header.${name}`, {
|
||||
value: strValue,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (props.userId) {
|
||||
baggage = baggage.setEntry("langfuse.user.id", { value: props.userId });
|
||||
}
|
||||
if (props.projectId) {
|
||||
baggage = baggage.setEntry("langfuse.project.id", {
|
||||
value: props.projectId,
|
||||
});
|
||||
}
|
||||
|
||||
return opentelemetry.propagation.setBaggage(ctx, baggage);
|
||||
};
|
||||
@@ -41,13 +41,14 @@ export * from "./redis/dataRetentionProcessingQueue";
|
||||
export * from "./redis/coreDataS3ExportQueue";
|
||||
export * from "./redis/meteringDataPostgresExportQueue";
|
||||
export * from "./redis/experimentCreateQueue";
|
||||
export * from "./redis/dlxRetryQueue";
|
||||
export * from "./redis/dlqRetryQueue";
|
||||
export * from "./auth/types";
|
||||
export * from "./queues";
|
||||
export * from "./orderByToPrisma";
|
||||
export * from "./filterToPrisma";
|
||||
export * from "./instrumentation";
|
||||
export * from "./logger";
|
||||
export * from "./headerPropagation";
|
||||
export * from "./queries";
|
||||
export * from "./repositories";
|
||||
export * from "./redis/evalExecutionQueue";
|
||||
@@ -55,6 +56,7 @@ export * from "./services/sessions-ui-table-service";
|
||||
export * from "./services/datasets-ui-table-service";
|
||||
export * from "./services/DashboardService";
|
||||
export * from "./services/TableViewService";
|
||||
export * from "./services/DefaultEvaluationModelService";
|
||||
|
||||
export * from "./data-deletion/ingestionFileDeletion";
|
||||
export * from "./s3";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { type Model } from "../../db";
|
||||
import { env } from "../../env";
|
||||
@@ -13,7 +13,6 @@ import { getClickhouseEntityType } from "../clickhouse/schemaUtils";
|
||||
import {
|
||||
getCurrentSpan,
|
||||
instrumentAsync,
|
||||
instrumentSync,
|
||||
recordDistribution,
|
||||
recordIncrement,
|
||||
traceException,
|
||||
@@ -39,6 +38,8 @@ const getS3StorageServiceClient = (bucketName: string): StorageService => {
|
||||
endpoint: env.LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT,
|
||||
region: env.LANGFUSE_S3_EVENT_UPLOAD_REGION,
|
||||
forcePathStyle: env.LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE === "true",
|
||||
awsSse: env.LANGFUSE_S3_EVENT_UPLOAD_SSE,
|
||||
awsSseKmsKeyId: env.LANGFUSE_S3_EVENT_UPLOAD_SSE_KMS_KEY_ID,
|
||||
});
|
||||
}
|
||||
return s3StorageServiceClient;
|
||||
@@ -78,11 +79,13 @@ const getDelay = (delay: number | null) => {
|
||||
* @param input - Batch of IngestionEventType. Will validate the types first thing and return errors if they are invalid.
|
||||
* @param authCheck - AuthHeaderValidVerificationResult
|
||||
* @param delay - (Optional) Delay in ms to wait before processing events in the batch.
|
||||
* @param source - (Optional) Source of the events for metrics tracking (e.g., "otel", "api").
|
||||
*/
|
||||
export const processEventBatch = async (
|
||||
input: unknown[],
|
||||
authCheck: AuthHeaderValidVerificationResult,
|
||||
delay: number | null = null,
|
||||
source: "api" | "otel" = "api",
|
||||
): Promise<{
|
||||
successes: { id: string; status: number }[];
|
||||
errors: {
|
||||
@@ -94,8 +97,10 @@ export const processEventBatch = async (
|
||||
}> => {
|
||||
// add context of api call to the span
|
||||
const currentSpan = getCurrentSpan();
|
||||
recordIncrement("langfuse.ingestion.event", input.length);
|
||||
recordDistribution("langfuse.ingestion.event_distribution", input.length);
|
||||
recordIncrement("langfuse.ingestion.event", input.length, { source });
|
||||
recordDistribution("langfuse.ingestion.event_distribution", input.length, {
|
||||
source,
|
||||
});
|
||||
|
||||
currentSpan?.setAttribute("langfuse.ingestion.batch_size", input.length);
|
||||
currentSpan?.setAttribute(
|
||||
@@ -117,19 +122,7 @@ export const processEventBatch = async (
|
||||
|
||||
const batch: z.infer<typeof ingestionEvent>[] = input
|
||||
.flatMap((event) => {
|
||||
const parsed = instrumentSync(
|
||||
{ name: "ingestion-zod-parse-individual-event" },
|
||||
(span) => {
|
||||
const parsedBody = ingestionEvent.safeParse(event);
|
||||
if (parsedBody.data?.id !== undefined) {
|
||||
span.setAttribute(
|
||||
"langfuse.ingestion.entity.id",
|
||||
parsedBody.data.id,
|
||||
);
|
||||
}
|
||||
return parsedBody;
|
||||
},
|
||||
);
|
||||
const parsed = ingestionEvent.safeParse(event);
|
||||
if (!parsed.success) {
|
||||
validationErrors.push({
|
||||
id:
|
||||
@@ -236,10 +229,13 @@ export const processEventBatch = async (
|
||||
throw new Error("Redis not initialized, aborting event processing");
|
||||
}
|
||||
|
||||
const queue = IngestionQueue.getInstance();
|
||||
await Promise.all(
|
||||
Object.keys(sortedBatchByEventBodyId).map(async (id) =>
|
||||
queue
|
||||
Object.keys(sortedBatchByEventBodyId).map(async (id) => {
|
||||
const eventData = sortedBatchByEventBodyId[id];
|
||||
const shardingKey = `${authCheck.scope.projectId}-${eventData.eventBodyId}`;
|
||||
const queue = IngestionQueue.getInstance({ shardingKey });
|
||||
|
||||
return queue
|
||||
? queue.add(
|
||||
QueueJobs.IngestionJob,
|
||||
{
|
||||
@@ -248,9 +244,12 @@ export const processEventBatch = async (
|
||||
name: QueueJobs.IngestionJob as const,
|
||||
payload: {
|
||||
data: {
|
||||
type: sortedBatchByEventBodyId[id].type,
|
||||
eventBodyId: sortedBatchByEventBodyId[id].eventBodyId,
|
||||
fileKey: sortedBatchByEventBodyId[id].key,
|
||||
type: eventData.type,
|
||||
eventBodyId: eventData.eventBodyId,
|
||||
fileKey: eventData.key,
|
||||
skipS3List:
|
||||
source === "otel" &&
|
||||
getClickhouseEntityType(eventData.type) === "observation",
|
||||
},
|
||||
authCheck: authCheck as {
|
||||
validKey: true;
|
||||
@@ -263,8 +262,8 @@ export const processEventBatch = async (
|
||||
},
|
||||
{ delay: getDelay(delay) },
|
||||
)
|
||||
: Promise.reject("Failed to instantiate queue"),
|
||||
),
|
||||
: Promise.reject("Failed to instantiate queue");
|
||||
}),
|
||||
);
|
||||
|
||||
return aggregateBatchResult(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import lodash from "lodash";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { NonEmptyString, jsonSchema } from "../../utils/zod";
|
||||
import { ModelUsageUnit } from "../../constants";
|
||||
@@ -20,7 +20,7 @@ export const Usage = z.object({
|
||||
input: z.number().int().nullish(),
|
||||
output: z.number().int().nullish(),
|
||||
total: z.number().int().nullish(),
|
||||
unit: z.nativeEnum(ModelUsageUnit).nullish(),
|
||||
unit: z.enum(ModelUsageUnit).nullish(),
|
||||
inputCost: z.number().nullish(),
|
||||
outputCost: z.number().nullish(),
|
||||
totalCost: z.number().nullish(),
|
||||
@@ -30,7 +30,7 @@ const MixedUsage = z.object({
|
||||
input: z.number().int().nullish(),
|
||||
output: z.number().int().nullish(),
|
||||
total: z.number().int().nullish(),
|
||||
unit: z.nativeEnum(ModelUsageUnit).nullish(),
|
||||
unit: z.enum(ModelUsageUnit).nullish(),
|
||||
promptTokens: z.number().int().nullish(),
|
||||
completionTokens: z.number().int().nullish(),
|
||||
totalTokens: z.number().int().nullish(),
|
||||
@@ -128,8 +128,7 @@ const OpenAICompletionUsageSchema = z
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.pipe(RawUsageDetails);
|
||||
});
|
||||
|
||||
// The new OpenAI Response API uses a new Usage schema that departs from the Completion API Usage schema
|
||||
const OpenAIResponseUsageSchema = z
|
||||
@@ -184,8 +183,7 @@ const OpenAIResponseUsageSchema = z
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.pipe(RawUsageDetails);
|
||||
});
|
||||
|
||||
export const UsageDetails = z
|
||||
.union([
|
||||
@@ -266,7 +264,7 @@ export const CreateGenerationBody = CreateSpanBody.extend({
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.array(z.string()),
|
||||
z.record(z.string()),
|
||||
z.record(z.string(), z.string()),
|
||||
])
|
||||
.nullish(),
|
||||
)
|
||||
@@ -296,7 +294,7 @@ export const UpdateGenerationBody = UpdateSpanBody.extend({
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.array(z.string()),
|
||||
z.record(z.string()),
|
||||
z.record(z.string(), z.string()),
|
||||
])
|
||||
.nullish(),
|
||||
)
|
||||
|
||||
@@ -162,7 +162,7 @@ function validateConfigAgainstBody(
|
||||
});
|
||||
|
||||
if (!rangeValidation.success) {
|
||||
const errorDetails = rangeValidation.error.errors
|
||||
const errorDetails = rangeValidation.error.issues
|
||||
.map((error) => `${error.path.join(".")} - ${error.message}`)
|
||||
.join(", ");
|
||||
|
||||
|
||||
@@ -43,6 +43,14 @@ export async function instrumentAsync<T>(
|
||||
},
|
||||
activeContext,
|
||||
async (span) => {
|
||||
const baggage = opentelemetry.propagation.getBaggage(
|
||||
opentelemetry.context.active(),
|
||||
);
|
||||
if (baggage) {
|
||||
baggage
|
||||
.getAllEntries()
|
||||
.forEach(([k, v]) => span.setAttribute(k, v.value));
|
||||
}
|
||||
try {
|
||||
const result = await callback(span);
|
||||
span.end();
|
||||
@@ -77,6 +85,14 @@ export function instrumentSync<T>(
|
||||
},
|
||||
activeContext,
|
||||
(span) => {
|
||||
const baggage = opentelemetry.propagation.getBaggage(
|
||||
opentelemetry.context.active(),
|
||||
);
|
||||
if (baggage) {
|
||||
baggage
|
||||
.getAllEntries()
|
||||
.forEach(([k, v]) => span.setAttribute(k, v.value));
|
||||
}
|
||||
try {
|
||||
const result = callback(span);
|
||||
span.end();
|
||||
@@ -141,30 +157,6 @@ export const traceException = (
|
||||
});
|
||||
};
|
||||
|
||||
export const addUserToSpan = (
|
||||
attributes: {
|
||||
userId?: string;
|
||||
projectId?: string;
|
||||
email?: string;
|
||||
orgId?: string;
|
||||
plan?: string;
|
||||
},
|
||||
span?: opentelemetry.Span,
|
||||
) => {
|
||||
const activeSpan = span ?? getCurrentSpan();
|
||||
|
||||
if (!activeSpan) {
|
||||
return;
|
||||
}
|
||||
|
||||
attributes.userId && activeSpan.setAttribute("user.id", attributes.userId);
|
||||
attributes.email && activeSpan.setAttribute("user.email", attributes.email);
|
||||
attributes.projectId &&
|
||||
activeSpan.setAttribute("project.id", attributes.projectId);
|
||||
attributes.orgId && activeSpan.setAttribute("org.id", attributes.orgId);
|
||||
attributes.plan && activeSpan.setAttribute("org.plan", attributes.plan);
|
||||
};
|
||||
|
||||
export const getTracer = (name: string) => opentelemetry.trace.getTracer(name);
|
||||
|
||||
const cloudWatchClient = new CloudWatchClient();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ZodSchema } from "zod";
|
||||
import { type ZodSchema } from "zod/v4";
|
||||
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { ChatVertexAI } from "@langchain/google-vertexai";
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
StringOutputParser,
|
||||
} from "@langchain/core/output_parsers";
|
||||
import { IterableReadableStream } from "@langchain/core/utils/stream";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { ChatOpenAI, AzureChatOpenAI } from "@langchain/openai";
|
||||
import GCPServiceAccountKeySchema, {
|
||||
BedrockConfigSchema,
|
||||
BedrockCredentialSchema,
|
||||
@@ -219,7 +219,7 @@ export async function fetchLLMCompletion(
|
||||
timeout: 1000 * 60 * 2, // 2 minutes timeout
|
||||
});
|
||||
} else if (modelParams.adapter === LLMAdapter.Azure) {
|
||||
chatModel = new ChatOpenAI({
|
||||
chatModel = new AzureChatOpenAI({
|
||||
azureOpenAIApiKey: apiKey,
|
||||
azureOpenAIBasePath: baseURL,
|
||||
azureOpenAIApiDeploymentName: modelParams.model,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LlmApiKeys } from "@prisma/client";
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { BedrockConfigSchema } from "../../interfaces/customLLMProviderConfigSchemas";
|
||||
import { TokenCountDelegate } from "../ingestion/processEventBatch";
|
||||
import { AuthHeaderValidVerificationResult } from "../auth/types";
|
||||
@@ -28,7 +28,7 @@ export const JSONSchemaFormSchema = z
|
||||
z
|
||||
.object({
|
||||
type: z.literal("object"),
|
||||
properties: z.record(z.any()),
|
||||
properties: z.record(z.string(), z.any()),
|
||||
required: z.array(z.string()).optional(),
|
||||
additionalProperties: z.boolean().optional(),
|
||||
})
|
||||
@@ -71,7 +71,13 @@ export const OpenAIToolCallSchema = z.object({
|
||||
z.record(z.string(), z.unknown()),
|
||||
z
|
||||
.string()
|
||||
.transform((v) => JSON.parse(v))
|
||||
.transform((v) => {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch {
|
||||
return v;
|
||||
}
|
||||
})
|
||||
.pipe(z.record(z.string(), z.unknown())),
|
||||
]),
|
||||
}),
|
||||
@@ -162,7 +168,7 @@ export const ToolResultMessageSchema = z.object({
|
||||
});
|
||||
export type ToolResultMessage = z.infer<typeof ToolResultMessageSchema>;
|
||||
|
||||
export const ChatMessageDefaultRoleSchema = z.nativeEnum(ChatMessageRole);
|
||||
export const ChatMessageDefaultRoleSchema = z.enum(ChatMessageRole);
|
||||
export const ChatMessageSchema = z.union([
|
||||
SystemMessageSchema,
|
||||
DeveloperMessageSchema,
|
||||
@@ -297,6 +303,8 @@ export type OpenAIModel = (typeof openAIModels)[number];
|
||||
// NOTE: Update docs page when changing this! https://langfuse.com/docs/playground#openai-playground--anthropic-playground
|
||||
// WARNING: The first entry in the array is chosen as the default model to add LLM API keys
|
||||
export const anthropicModels = [
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-sonnet-20240620",
|
||||
@@ -311,7 +319,8 @@ export const anthropicModels = [
|
||||
|
||||
// WARNING: The first entry in the array is chosen as the default model to add LLM API keys
|
||||
export const vertexAIModels = [
|
||||
"gemini-2.5-pro-exp-03-25",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-001",
|
||||
@@ -324,7 +333,8 @@ export const vertexAIModels = [
|
||||
|
||||
// WARNING: The first entry in the array is chosen as the default model to add LLM API keys. Make sure it supports top_p, max_tokens and temperature.
|
||||
export const googleAIStudioModels = [
|
||||
"gemini-2.5-pro-exp-03-25",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-lite-preview",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
@@ -360,7 +370,7 @@ export const LLMApiKeySchema = z
|
||||
projectId: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
adapter: z.nativeEnum(LLMAdapter),
|
||||
adapter: z.enum(LLMAdapter),
|
||||
provider: z.string(),
|
||||
displaySecretKey: z.string(),
|
||||
secretKey: z.string(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { decrypt } from "../../encryption";
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { env } from "../env";
|
||||
import winston from "winston";
|
||||
import { getCurrentSpan } from "./instrumentation";
|
||||
import { propagation, context } from "@opentelemetry/api";
|
||||
|
||||
const tracingFormat = function () {
|
||||
return winston.format((info) => {
|
||||
@@ -13,6 +14,12 @@ const tracingFormat = function () {
|
||||
info["trace_id"] = traceId;
|
||||
info["span_id"] = spanId;
|
||||
}
|
||||
const baggage = propagation.getBaggage(context.active());
|
||||
if (baggage) {
|
||||
const headerObj: Record<string, string> = {};
|
||||
baggage.getAllEntries().forEach(([k, v]) => (headerObj[k] = v.value));
|
||||
if (Object.keys(headerObj).length) info = { ...headerObj, ...info };
|
||||
}
|
||||
return info;
|
||||
})();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { singleFilter } from "../../../interfaces/filters";
|
||||
import { FilterCondition } from "../../../types";
|
||||
import { isValidTableName } from "../../clickhouse/schemaUtils";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
import { OrderByState } from "../../../interfaces/orderBy";
|
||||
import { UiColumnMappings } from "../../../tableDefinitions";
|
||||
import { logger } from "../../logger";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user