Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5eed6653a | ||
|
|
78aa8fdd26 | ||
|
|
9f0fbf8081 | ||
|
|
d63ce00194 | ||
|
|
9ca761bdc6 | ||
|
|
e2ec566fe4 | ||
|
|
bebc76a502 | ||
|
|
7bfbe19a8c | ||
|
|
e625053849 | ||
|
|
dc3530f195 | ||
|
|
1cf7c81386 | ||
|
|
7e31dda960 | ||
|
|
1f64991e7e | ||
|
|
577ad574d1 | ||
|
|
879ef9c8f9 | ||
|
|
a343d5b187 | ||
|
|
ac2b9eaa56 | ||
|
|
43e9cba99a | ||
|
|
601f431f69 | ||
|
|
4fd1f86f9d | ||
|
|
8245888e3c | ||
|
|
daf4e2fe02 | ||
|
|
129693fb69 | ||
|
|
204948db44 | ||
|
|
d42ba5fc99 | ||
|
|
97a539ced3 | ||
|
|
9d8dace197 | ||
|
|
bc2dc4d89c | ||
|
|
2dd5c8a8aa | ||
|
|
d17b21f04d | ||
|
|
c5a0f44bdd | ||
|
|
011b4912f2 | ||
|
|
a4d773066f | ||
|
|
25bdb1690b | ||
|
|
f8565d7772 | ||
|
|
a912057082 | ||
|
|
e0d3270f50 | ||
|
|
99e5a0b224 | ||
|
|
bccdee5410 | ||
|
|
738cbbc8cd | ||
|
|
920c52bb06 | ||
|
|
4f134790af | ||
|
|
21b3ce3c82 | ||
|
|
0531b57e1a | ||
|
|
7b857a0dc4 | ||
|
|
6c97fe3c04 | ||
|
|
1d69cbd41b | ||
|
|
ab26692913 | ||
|
|
b791544864 | ||
|
|
b35583056b | ||
|
|
4504530e3d | ||
|
|
45eed4b5c0 | ||
|
|
9fa31ba68e | ||
|
|
ea35c25269 | ||
|
|
c427791070 | ||
|
|
25220aef55 | ||
|
|
8c02d5dc22 | ||
|
|
0b60076871 | ||
|
|
b28a83531b | ||
|
|
aa4d34ae66 | ||
|
|
cca352ad13 | ||
|
|
58c96cada7 | ||
|
|
6f93389936 | ||
|
|
b8d9586490 | ||
|
|
534f5696ad | ||
|
|
4cebe2831c | ||
|
|
45a5f3b8a2 | ||
|
|
8dea9b6a3a | ||
|
|
c5b781d3f6 | ||
|
|
07469c928d | ||
|
|
5b10110dfe | ||
|
|
0eb5d1c3c0 | ||
|
|
1585bf5e14 | ||
|
|
dec6d6f975 | ||
|
|
36fa7ea15e | ||
|
|
6a4d56a5a9 | ||
|
|
9f0a40949e | ||
|
|
10fdd9e172 | ||
|
|
fc58add111 | ||
|
|
10993838d5 | ||
|
|
148cd29e9f | ||
|
|
7a353e99af | ||
|
|
dc52106feb | ||
|
|
b2f8eb7078 | ||
|
|
026d8e2ba0 | ||
|
|
9a9f0b1743 | ||
|
|
74a510fbde | ||
|
|
c23b226e62 | ||
|
|
2f50b38a68 | ||
|
|
451ae15e00 | ||
|
|
e34d81578b | ||
|
|
214c9c7ed2 | ||
|
|
961b3bfa8d | ||
|
|
a1dd5b22a2 | ||
|
|
49950f9706 | ||
|
|
cfdd0fdb73 | ||
|
|
891e7e9716 | ||
|
|
5b407dad53 | ||
|
|
26e3bf9a44 | ||
|
|
1f02e364e1 | ||
|
|
90dca15d86 | ||
|
|
e3f8dc8bb3 | ||
|
|
585ede0919 | ||
|
|
0db425d120 | ||
|
|
09e33c3059 | ||
|
|
66226011af | ||
|
|
571698ab2e | ||
|
|
312066f735 | ||
|
|
5abeaf8adb | ||
|
|
fac3c732de | ||
|
|
5e2e3bb5fc | ||
|
|
0564df8e51 | ||
|
|
a2801a3be9 | ||
|
|
075eb58ecb | ||
|
|
674d66d179 | ||
|
|
163f2a02ff | ||
|
|
075836f210 | ||
|
|
543b6ee0f2 | ||
|
|
8c8c488e4d | ||
|
|
6a0e0a4221 | ||
|
|
1b01a267df | ||
|
|
69a9146894 | ||
|
|
380403e8ed | ||
|
|
296a6c3ee6 | ||
|
|
62904a9563 | ||
|
|
90247ab64c | ||
|
|
5cf91c60d9 | ||
|
|
559ba6d05d | ||
|
|
f071be69b6 | ||
|
|
52c261b422 | ||
|
|
6f0a43ec65 | ||
|
|
301bd6b569 | ||
|
|
56fd3df2d2 | ||
|
|
db433e2b72 | ||
|
|
e452004a6a | ||
|
|
bce026d8b2 | ||
|
|
fbdf12bfa3 | ||
|
|
78f59bc543 | ||
|
|
ea338ed83d | ||
|
|
8cf67fe329 | ||
|
|
e3b23ea5ec | ||
|
|
7be2791105 | ||
|
|
683aae0069 | ||
|
|
1c8ffc607d | ||
|
|
9c203e8b8a | ||
|
|
c32cccce52 | ||
|
|
f1f8da5b74 | ||
|
|
c23447a624 | ||
|
|
5e2225de46 | ||
|
|
bb9d118853 | ||
|
|
df57ff60d6 | ||
|
|
bae2c5a65d | ||
|
|
3efa696513 | ||
|
|
58ebf006b8 | ||
|
|
16743363dc | ||
|
|
ebf0e35073 | ||
|
|
2c4799340f | ||
|
|
5612c6a9a5 | ||
|
|
b62962ca08 | ||
|
|
3a46283bf2 | ||
|
|
07801180f8 | ||
|
|
14831902ef | ||
|
|
d377f02a49 | ||
|
|
e07120b163 | ||
|
|
506482dbe4 | ||
|
|
ec61f4a420 | ||
|
|
4db08a2960 | ||
|
|
2351d0d370 | ||
|
|
99b3401549 | ||
|
|
6fb796df40 | ||
|
|
846d6e6c37 | ||
|
|
1fa4fc6329 | ||
|
|
a1a48d5e7b | ||
|
|
fcb7563763 | ||
|
|
ae754b146d | ||
|
|
49343b9a0c | ||
|
|
76bf5c0f4e | ||
|
|
e91a29be61 | ||
|
|
81694363a2 | ||
|
|
5998266680 | ||
|
|
462e8e847d | ||
|
|
d7c186858b | ||
|
|
e686aedac9 | ||
|
|
85f75a5e8e | ||
|
|
1ea643300d | ||
|
|
596486c1ec | ||
|
|
cb65391c2d | ||
|
|
419e07260d | ||
|
|
825f030e81 | ||
|
|
34ded9c927 | ||
|
|
c935d4ae73 | ||
|
|
e7283ac06e | ||
|
|
20d0106626 | ||
|
|
849591fdf2 | ||
|
|
0c7b50e564 | ||
|
|
37b4f43351 | ||
|
|
7a3b0379e5 | ||
|
|
f458d7626c | ||
|
|
258dde4691 | ||
|
|
f15246d9df | ||
|
|
edb43a24ab | ||
|
|
c45ae1b140 | ||
|
|
90eeeeaf9d | ||
|
|
66accb563c | ||
|
|
9f2e8906d0 | ||
|
|
c5d61b7ed2 | ||
|
|
f95dd872e6 | ||
|
|
04339741a9 | ||
|
|
9c85e46996 | ||
|
|
4a7236451f | ||
|
|
5cb5204181 | ||
|
|
c19066afa0 | ||
|
|
bbb9dc285d | ||
|
|
b750acba06 | ||
|
|
bb06e079eb | ||
|
|
5f711780e0 | ||
|
|
0f58ea1ebf | ||
|
|
ec70ca3951 | ||
|
|
142d3a1612 | ||
|
|
1dee7092b3 | ||
|
|
83b64f5e77 | ||
|
|
48b05247c8 | ||
|
|
9732262466 | ||
|
|
e51e2df2dc | ||
|
|
74d1dfe5ac | ||
|
|
5633a8867e | ||
|
|
6a8bdae22e | ||
|
|
dbf994f9bb | ||
|
|
4d5c288d1e | ||
|
|
cf119fb9cc | ||
|
|
5af8c4e400 | ||
|
|
835a19bbd2 | ||
|
|
ae91a98ad9 | ||
|
|
e38cc24205 | ||
|
|
7ab45ec685 | ||
|
|
b101bf48d3 | ||
|
|
4c4f0ff3c0 | ||
|
|
a42ca96225 | ||
|
|
f63420827b | ||
|
|
e6fd056ae5 | ||
|
|
4708fe4f47 | ||
|
|
362246b616 | ||
|
|
b3de5fd71a | ||
|
|
ad236ecc99 | ||
|
|
beb2063dcb | ||
|
|
4de10298dc | ||
|
|
f9924975fa | ||
|
|
98774cbd61 | ||
|
|
80361ef53f | ||
|
|
8350507434 | ||
|
|
0a976edb70 | ||
|
|
6a67c93010 | ||
|
|
e51ee05d9c | ||
|
|
565c561715 | ||
|
|
d1338ad5e1 | ||
|
|
52d4fe37de | ||
|
|
67ef7abb63 | ||
|
|
727ae71a93 | ||
|
|
a8eabbc96d | ||
|
|
51befcad0f | ||
|
|
ee8b5e4998 | ||
|
|
9bb9f7fa39 | ||
|
|
4cc10840ab | ||
|
|
311cd900fb | ||
|
|
92f14fd297 | ||
|
|
42306e5247 | ||
|
|
5bb1b6de54 | ||
|
|
dae0ee6165 | ||
|
|
31fb304a5e | ||
|
|
d6dac50734 | ||
|
|
0cda8e2e21 | ||
|
|
3f4fd2ff5e | ||
|
|
17f0a10b23 | ||
|
|
6c80b5b4a0 | ||
|
|
91b56518c6 | ||
|
|
257c4136fe | ||
|
|
94e5f4b2e7 | ||
|
|
f09c363aaf | ||
|
|
d0c317b423 | ||
|
|
5bcb6b5b38 | ||
|
|
3806cb72a1 | ||
|
|
cbfc78d0e3 | ||
|
|
5d87047577 | ||
|
|
7a3e5ba0c8 | ||
|
|
df1ec4d81e | ||
|
|
68a884c5ed | ||
|
|
7bcfb9a57e | ||
|
|
0e360759ec | ||
|
|
0510a352a3 | ||
|
|
7854a7e41f | ||
|
|
f0fee7b0ce | ||
|
|
52fd2b9ed8 | ||
|
|
3ba75e57a0 | ||
|
|
a42cf72d2a | ||
|
|
35171cc77d | ||
|
|
6a5f221567 | ||
|
|
c9c61c4b8e | ||
|
|
8b6186f191 | ||
|
|
73fc0e71a3 | ||
|
|
40152d1f3c | ||
|
|
6cfb78e06e | ||
|
|
4f01e773b3 | ||
|
|
2a83fdbc9a | ||
|
|
84dd3fa63b | ||
|
|
fdf70cdde9 | ||
|
|
07f0125e7c | ||
|
|
bacd44abb1 | ||
|
|
cb032047d3 | ||
|
|
3ecbcc1670 | ||
|
|
85774535ae | ||
|
|
db15742d29 | ||
|
|
cc1c847728 | ||
|
|
a6bf28495c | ||
|
|
0391b9a263 | ||
|
|
4fc2e33058 | ||
|
|
9a6fa8747b | ||
|
|
bd0b204d18 | ||
|
|
94586e3f62 | ||
|
|
63349976fa | ||
|
|
8d092d0e18 | ||
|
|
c8c031e0bd | ||
|
|
b616b0d260 | ||
|
|
91d929646e | ||
|
|
bfdbd93997 | ||
|
|
2916b4228d | ||
|
|
e5a04751e1 | ||
|
|
bb554b38af | ||
|
|
cfc8161473 | ||
|
|
aecd8eff2d | ||
|
|
74c59f58c2 | ||
|
|
e073ec9a21 | ||
|
|
5920f3a6c7 | ||
|
|
c2a0b381d4 | ||
|
|
7598c95fc3 | ||
|
|
35f6f5f0fc | ||
|
|
da23a5ad0b | ||
|
|
5adac14049 | ||
|
|
234f131493 | ||
|
|
c87e3f88fc | ||
|
|
b0aeb8c454 | ||
|
|
a71ac105b9 | ||
|
|
225d64d0f8 | ||
|
|
327018eb09 | ||
|
|
fe8ba75ff3 | ||
|
|
74e8c0fe58 | ||
|
|
4f57af8ea1 | ||
|
|
4132335c96 | ||
|
|
67dae75e3a | ||
|
|
ed0a07a958 | ||
|
|
0a63cc61fc | ||
|
|
c385121406 | ||
|
|
7d7932e024 | ||
|
|
b26362cbca | ||
|
|
9b8095f8f5 | ||
|
|
8b76617019 | ||
|
|
93e50f4720 | ||
|
|
c1388c8699 | ||
|
|
43fc305d5b | ||
|
|
ebd4cf3e8a | ||
|
|
5dfc821575 | ||
|
|
3965a5174f | ||
|
|
df237a11d5 | ||
|
|
373cefdcb5 | ||
|
|
b92a8c6d81 | ||
|
|
68422d404c | ||
|
|
d42c4e5e2f | ||
|
|
0d2586e4f2 | ||
|
|
ff40e79143 | ||
|
|
bd04f6b186 | ||
|
|
d107f98654 | ||
|
|
de9fa8199a | ||
|
|
0e1498a094 | ||
|
|
38f020e482 | ||
|
|
07e1d4e960 | ||
|
|
602d00a5bb | ||
|
|
7395bd8d83 | ||
|
|
63d164ad52 | ||
|
|
f1b915c8b7 | ||
|
|
e3886b94c1 | ||
|
|
44376c2a9f | ||
|
|
2e85c9ac15 | ||
|
|
7cc81deb65 | ||
|
|
a13ddd41ae |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(find:*)",
|
||||
"Bash(rg:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(head:*)",
|
||||
"Bash(tail:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
[codespell]
|
||||
skip = .git,*.pdf,*.svg,package-lock.json,*.prisma,pnpm-lock.yaml
|
||||
ignore-words-list = afterall,vertx,notIn
|
||||
ignore-words-list = afterall,vertx,notIn,alue
|
||||
|
||||
|
||||
+13
-6
@@ -12,8 +12,15 @@ RUN apt-get update && \
|
||||
postgresql-client \
|
||||
redis-tools \
|
||||
less nano \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ---------- Docker -----------------------------------------------------------
|
||||
# Install Docker for background agents that need container capabilities
|
||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && \
|
||||
sh get-docker.sh && \
|
||||
rm get-docker.sh
|
||||
|
||||
# ---------- pnpm -------------------------------------------------------------
|
||||
# Langfuse monorepo relies on pnpm 9.5.0 (see CONTRIBUTING.md)
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
@@ -23,17 +30,17 @@ RUN corepack enable && \
|
||||
|
||||
# ---------- golang-migrate ----------------------------------------------------
|
||||
# CLI used for database migrations during development.
|
||||
ENV MIGRATE_VERSION=4.18.2
|
||||
ENV MIGRATE_VERSION=4.18.3
|
||||
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
|
||||
# Create non-root user with sudo privileges and docker group access
|
||||
RUN useradd -ms /bin/bash ubuntu && \
|
||||
usermod -aG sudo ubuntu && \
|
||||
usermod -aG docker ubuntu && \
|
||||
echo "ubuntu ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
# Pre-create pnpm store and set correct ownership to avoid first-run cost & permission issues
|
||||
RUN pnpm store path > /dev/null && \
|
||||
|
||||
@@ -4,5 +4,11 @@
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"start": "pnpm dx-f"
|
||||
"start": "sudo service docker start",
|
||||
"terminals": [
|
||||
{
|
||||
"name": "dev server",
|
||||
"command": "pnpm run dx-f"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ alwaysApply: true
|
||||
---
|
||||
# General rules
|
||||
|
||||
- Linting in this repo only works if the development server is running
|
||||
- Linting in this repo only works if the development server is running
|
||||
- Always run the full mono-repo via `pnpm run dx` (use `pnpm dx-f` when you run this in a background agent). Thereby the database will also be seeded.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
## Project setup
|
||||
- This project is a Turborepo monorepo
|
||||
- We build two containers, web (./web) and worker (./worker)
|
||||
- We have shared code between these two in the shared package (./packages/shared). For that package, we have different entry points [package.json](mdc:packages/shared/package.json).
|
||||
|
||||
|
||||
## Domain layer
|
||||
The most important domain objects are in [observations.ts](mdc:packages/shared/src/domain/observations.ts), [traces.ts](mdc:packages/shared/src/domain/traces.ts), [scores.ts](mdc:packages/shared/src/domain/scores.ts).
|
||||
|
||||
|
||||
## Database schema
|
||||
We use Postgres and Clickhouse.
|
||||
- The postgres schema is in [schema.prisma](mdc:packages/shared/prisma/schema.prisma)
|
||||
- The clickhouse schema is in [0001_traces.up.sql](mdc:packages/shared/clickhouse/migrations/clustered/0001_traces.up.sql), [0002_observations.up.sql](mdc:packages/shared/clickhouse/migrations/clustered/0002_observations.up.sql), [0003_scores.up.sql](mdc:packages/shared/clickhouse/migrations/clustered/0003_scores.up.sql)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
description: How to create new public api routes for Langfuse
|
||||
globs:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
@@ -13,4 +13,5 @@ alwaysApply: false
|
||||
- Add end-to-end test, similar to [datasets-api.servertest.ts](mdc:web/src/__tests__/async/datasets-api.servertest.ts)
|
||||
- Add fern configuration in /fern, learn more about structure here: https://buildwithfern.com/learn/api-definition/fern/overview
|
||||
- Prompt user to regenerate the OpenAPI spec via the fern CLI
|
||||
- Pagination starts at 1, query typing defined in publicApiPaginationZod, return meta in paginationMetaResponseZod
|
||||
- Pagination starts at 1, query typing defined in publicApiPaginationZod, return meta in paginationMetaResponseZod
|
||||
- For tests, please look at [this directory](mdc:web/src/__tests__/async/) for examples
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# Writing tests
|
||||
|
||||
- When writing tests, focus on decoupling each `it` or `test` block to ensure that they can run independently and concurrently. Tests must never depend on the action or outcome of previous or subsequent tests.
|
||||
- When writing tests, especially in the __tests__/async directory, ensure that you avoid `pruneDatabase` calls.
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "langfuse-development",
|
||||
"forwardPorts": [3000, 5432, 6379, 8123, 9000],
|
||||
"onCreateCommand": "npm install -g pnpm@9.5.0",
|
||||
"postCreateCommand": "curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz && git restore LICENSE README.md && chmod +x migrate && sudo mv migrate /usr/bin && cp .env.dev.example .env && npm install -g @anthropic-ai/claude-code && pnpm i"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Dev container Dockerfile
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:20-bookworm
|
||||
|
||||
# Install golang-migrate for database migrations
|
||||
RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz && \
|
||||
chmod +x migrate && \
|
||||
mv migrate /usr/local/bin/migrate
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm@9.5.0
|
||||
|
||||
# Install Claude Code CLI
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "langfuse-development",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"forwardPorts": [3000, 5432, 6379, 8123, 9000],
|
||||
"postCreateCommand": "cp .env.dev.example .env && pnpm i"
|
||||
}
|
||||
@@ -68,6 +68,7 @@ LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
|
||||
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
|
||||
|
||||
LANGFUSE_USE_AZURE_BLOB=true
|
||||
LANGFUSE_AZURE_SKIP_CONTAINER_CHECK=false
|
||||
|
||||
# Set during docker build of application
|
||||
# Used to disable environment verification at build time
|
||||
|
||||
@@ -76,9 +76,18 @@ 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
|
||||
LANGFUSE_TRACE_UPSERT_QUEUE_SHARD_COUNT=4
|
||||
|
||||
# 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"
|
||||
|
||||
# Use the following settings to enforce running the new AMTs during the tests
|
||||
LANGFUSE_EXPERIMENT_INSERT_INTO_AGGREGATING_MERGE_TREES="true"
|
||||
LANGFUSE_EXPERIMENT_COMPARE_READ_FROM_AGGREGATING_MERGE_TREES="true"
|
||||
LANGFUSE_EXPERIMENT_WHITELISTED_PROJECT_IDS="7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"
|
||||
LANGFUSE_EXPERIMENT_ADD_QUERY_RESULT_TO_SPAN_PROJECT_IDS="7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"
|
||||
LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT="true"
|
||||
LANGFUSE_EXPERIMENT_SAMPLING_RATE=1
|
||||
@@ -83,3 +83,8 @@ NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT="false"
|
||||
# For SDK integration tests to pass, decrease the ingestion queue delay by uncommenting the env vars:
|
||||
# LANGFUSE_INGESTION_QUEUE_DELAY_MS=10
|
||||
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=10
|
||||
|
||||
# Slack credentials for development
|
||||
SLACK_CLIENT_ID=your_slack_client_id
|
||||
SLACK_CLIENT_SECRET=your_slack_client_secret
|
||||
SLACK_STATE_SECRET=your_slack_state_secret
|
||||
|
||||
@@ -172,6 +172,7 @@ OTEL_SERVICE_NAME="langfuse"
|
||||
# REDIS_HOST=
|
||||
# REDIS_PORT=
|
||||
# REDIS_AUTH=
|
||||
# REDIS_USERNAME=default
|
||||
# REDIS_CONNECTION_STRING=
|
||||
# REDIS_ENABLE_AUTO_PIPELINING=
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Test Environment Configuration
|
||||
# Copy this file to .env.test for test database isolation
|
||||
# Only overrides specific test variables - other values inherited from .env
|
||||
|
||||
# PostgreSQL - Test Database
|
||||
DIRECT_URL="postgresql://postgres:postgres@localhost:5432/langfuse_test"
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/langfuse_test"
|
||||
|
||||
# ClickHouse - Use Default Database for now, nothing set
|
||||
|
||||
# Redis - Test Database (database 1 for isolation)
|
||||
REDIS_CONNECTION_STRING="redis://:myredissecret@127.0.0.1:6379/1"
|
||||
@@ -28,7 +28,7 @@ Fixes # (issue)
|
||||
<!-- Remove bullet points below that don't apply to you -->
|
||||
|
||||
- I haven't read the [contributing guide](https://github.com/langfuse/langfuse/blob/main/CONTRIBUTING.md)
|
||||
- My code doesn't follow the style guidelines of this project (`npm run prettier`)
|
||||
- My code doesn't follow the style guidelines of this project (`pnpm run format`)
|
||||
- I haven't commented my code, particularly in hard-to-understand areas
|
||||
- I haven't checked if my PR needs changes to the documentation
|
||||
- I haven't checked if my changes generate no new warnings (`npm run lint`)
|
||||
|
||||
@@ -36,6 +36,7 @@ updates:
|
||||
patterns:
|
||||
- "express"
|
||||
- "@types/express"
|
||||
- "@types/express-serve-static-core"
|
||||
observability:
|
||||
patterns:
|
||||
- "dd-trace"
|
||||
|
||||
@@ -15,10 +15,6 @@ 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
|
||||
|
||||
@@ -52,6 +52,49 @@ jobs:
|
||||
- name: lint web
|
||||
run: pnpm run lint
|
||||
|
||||
prettier-check:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- pre-job
|
||||
if: needs.pre-job.outputs.should_skip != 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
- name: install dependencies
|
||||
run: |
|
||||
pnpm i
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
- name: Check formatting on changed files
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
BASE_SHA=${{ github.event.pull_request.base.sha }}
|
||||
else
|
||||
BASE_SHA=$(git merge-base origin/main HEAD)
|
||||
fi
|
||||
|
||||
echo "Checking files changed from $BASE_SHA to HEAD"
|
||||
|
||||
# Get changed files
|
||||
CHANGED_FILES=$(git diff --name-only $BASE_SHA HEAD -- '*.js' '*.jsx' '*.ts' '*.tsx' '*.css' | tr '\n' ' ')
|
||||
|
||||
if [ -n "$CHANGED_FILES" ] && [ "$CHANGED_FILES" != " " ]; then
|
||||
echo "Files to check: $CHANGED_FILES"
|
||||
pnpm prettier --check --experimental-cli $CHANGED_FILES
|
||||
else
|
||||
echo "No JS/TS/CSS files changed - skipping prettier check"
|
||||
fi
|
||||
|
||||
test-docker-build:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
@@ -71,7 +114,9 @@ jobs:
|
||||
run: echo "NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
- name: Build and run both images from compose
|
||||
run: |
|
||||
docker compose -f docker-compose.build.yml up -d
|
||||
docker compose --progress plain --verbose -f docker-compose.build.yml build --print > /tmp/bake.json
|
||||
docker buildx bake -f /tmp/bake.json
|
||||
docker compose --progress plain -f docker-compose.build.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
- name: Ensure no unhealthy status
|
||||
run: |
|
||||
@@ -100,14 +145,10 @@ jobs:
|
||||
node-version: [20]
|
||||
postgres-version: [12, 15]
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- uses: pnpm/action-setup@v3
|
||||
@@ -133,6 +174,7 @@ jobs:
|
||||
cp .env.dev.example .env
|
||||
grep -v -e '^LANGFUSE_S3_BATCH_EXPORT_ENABLED=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev.example > .env
|
||||
echo "LANGFUSE_INGESTION_QUEUE_DELAY_MS=1" >> .env
|
||||
echo "LANGFUSE_CACHE_PROMPT_ENABLED=false" >> .env
|
||||
echo "LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS=1" >> .env
|
||||
- name: Run dev containers
|
||||
run: |
|
||||
@@ -178,14 +220,10 @@ jobs:
|
||||
postgres-version: [12, 15]
|
||||
deploy-mode: ["", "-azure", "-redis-cluster"]
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- uses: pnpm/action-setup@v3
|
||||
@@ -257,10 +295,6 @@ jobs:
|
||||
postgres-version: [12, 15]
|
||||
deploy-mode: ["", "-azure", "-redis-cluster"]
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
@@ -282,7 +316,7 @@ jobs:
|
||||
pnpm install
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Load default env
|
||||
@@ -343,7 +377,7 @@ jobs:
|
||||
cp .env.dev.example web/.env
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Run + migrate
|
||||
@@ -397,14 +431,12 @@ jobs:
|
||||
pnpm install
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.2/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
echo "LANGFUSE_CACHE_API_KEY_ENABLED=true" >> .env
|
||||
echo "LANGFUSE_CACHE_PROMPT_ENABLED=true" >> .env
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
@@ -442,6 +474,7 @@ jobs:
|
||||
needs:
|
||||
[
|
||||
lint,
|
||||
prettier-check,
|
||||
tests-web-sync,
|
||||
tests-worker,
|
||||
e2e-tests,
|
||||
@@ -452,13 +485,21 @@ jobs:
|
||||
if: always()
|
||||
steps:
|
||||
- name: Successful deploy
|
||||
if: ${{ !(contains(needs.*.result, 'failure')) }}
|
||||
if: ${{ !(contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
|
||||
run: exit 0
|
||||
working-directory: .
|
||||
- name: Failing deploy
|
||||
if: ${{ contains(needs.*.result, 'failure') }}
|
||||
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||
run: exit 1
|
||||
working-directory: .
|
||||
- name: Notify Slack
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
if: always() && github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
notify_when: "failure"
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
push-docker-image:
|
||||
needs: all-ci-passed
|
||||
|
||||
+10
-47
@@ -1,65 +1,28 @@
|
||||
name: Snyk Container
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# Snyk cannot upload results in merge group. Hence, we only run on PRs and when pushingon the main branch https://github.com/github/codeql-action/issues/1572
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
snyk:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build a Docker image
|
||||
run: docker compose -f docker-compose.build.yml up -d
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Run Snyk to check Docker image for vulnerabilities (langfuse-web)
|
||||
continue-on-error: true
|
||||
- name: Scan web image with Snyk
|
||||
uses: snyk/actions/docker@master
|
||||
continue-on-error: true # let upload step always run
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
image: langfuse-langfuse-web
|
||||
args: --file=web/Dockerfile
|
||||
image: langfuse/langfuse # pulled from Docker Hub
|
||||
args: --severity-threshold=high # (any extra CLI flags, but NO --file)
|
||||
|
||||
# Workaround for https://github.com/github/codeql-action/issues/2187
|
||||
- name: Replace security-severity undefined for license-related findings
|
||||
run: "sed -i 's/\"security-severity\": \"undefined\"/\"security-severity\": \"0\"/g' snyk.sarif"
|
||||
|
||||
- name: Replace security-severity null for license-related findings
|
||||
run: "sed -i 's/\"security-severity\": \"null\"/\"security-severity\": \"0\"/g' snyk.sarif"
|
||||
|
||||
- name: Upload result to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: snyk.sarif
|
||||
category: web
|
||||
|
||||
- name: Run Snyk to check Docker image for vulnerabilities (langfuse-worker)
|
||||
continue-on-error: true
|
||||
- name: Scan worker image with Snyk
|
||||
uses: snyk/actions/docker@master
|
||||
continue-on-error: true # let upload step always run
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
image: langfuse-langfuse-worker
|
||||
args: --file=worker/Dockerfile
|
||||
|
||||
# Workaround for https://github.com/github/codeql-action/issues/2187
|
||||
- name: Replace security-severity undefined for license-related findings
|
||||
run: "sed -i 's/\"security-severity\": \"undefined\"/\"security-severity\": \"0\"/g' snyk.sarif"
|
||||
|
||||
- name: Replace security-severity null for license-related findings
|
||||
run: "sed -i 's/\"security-severity\": \"null\"/\"security-severity\": \"0\"/g' snyk.sarif"
|
||||
|
||||
- name: Upload result to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: snyk.sarif
|
||||
category: worker
|
||||
image: langfuse/langfuse-worker # pulled from Docker Hub
|
||||
args: --severity-threshold=high # (any extra CLI flags, but NO --file)
|
||||
|
||||
+6
-4
@@ -42,6 +42,7 @@ yarn-error.log*
|
||||
!.env.dev-azure.example
|
||||
!.env.dev-redis-cluster.example
|
||||
!.env.prod.example
|
||||
!.env.test.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
@@ -49,15 +50,13 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
/generated/typescript-server
|
||||
/generated
|
||||
|
||||
# openapi spec that is copied during build
|
||||
/public/openapi*.yml
|
||||
|
||||
|
||||
# vscode
|
||||
.devcontainer
|
||||
|
||||
node_modules
|
||||
**/node_modules
|
||||
**/dist
|
||||
@@ -66,4 +65,7 @@ node_modules
|
||||
.yarn
|
||||
.turbo
|
||||
|
||||
web/test-results/*
|
||||
web/test-results/*
|
||||
|
||||
# local config files
|
||||
*.local.*
|
||||
|
||||
+4
-3
@@ -11,12 +11,13 @@ if [ "$current_branch" = "$protected_branch" ]; then
|
||||
echo "🚨 You are about to commit to the $protected_branch branch. Are you sure? (y/n)"
|
||||
read -r answer < /dev/tty
|
||||
if [ "$answer" != "${answer#[Yy]}" ]; then
|
||||
exit 0 # Commit will proceed
|
||||
# Commit approved, check formatting. On files changed, block commit
|
||||
pnpm run format:check
|
||||
else
|
||||
echo "Commit to $protected_branch branch has been canceled."
|
||||
exit 1 # Commit will be blocked
|
||||
fi
|
||||
fi
|
||||
|
||||
# If not the protected branch, proceed with the commit
|
||||
exit 0
|
||||
# If not the protected branch, check formatting (on changed files, block)
|
||||
pnpm run format:check
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Generated code
|
||||
packages/shared/prisma/generated
|
||||
@@ -8,6 +8,8 @@ Langfuse is an **open source LLM engineering** platform for developing, monitori
|
||||
|
||||
## Tests
|
||||
- Codex cannot run the test suite because it depends on Docker-based infrastructure that is unavailable in this environment.
|
||||
- When writing tests, focus on decoupling each `it` or `test` block to ensure that they can run independently and concurrently. Tests must never depend on the action or outcome of previous or subsequent tests.
|
||||
- When writing tests, especially in the __tests__/async directory, ensure that you avoid `pruneDatabase` calls.
|
||||
|
||||
## Cursor Rules
|
||||
- Additional folder-specific rules live in `.cursor/rules/`.
|
||||
|
||||
@@ -83,7 +83,8 @@ Depending on the file location (sync, async)
|
||||
`web` related tests must go into the `web/src/__tests__/` folder.
|
||||
```sh
|
||||
pnpm test-sync --testPathPattern="$FILE_LOCATION_PATTERN" --testNamePattern="$TEST_NAME_PATTERN"
|
||||
pnpm test-async --testPathPattern="$FILE_LOCATION_PATTERN" --testNamePattern="$TEST_NAME_PATTERN"
|
||||
# For tests in the async folder:
|
||||
pnpm test -- --testPathPattern="$FILE_LOCATION_PATTERN" --testNamePattern="$TEST_NAME_PATTERN"
|
||||
```
|
||||
|
||||
### Testing in the Worker Package
|
||||
@@ -94,6 +95,7 @@ pnpm run test --filter=worker -- $TEST_FILE_NAME -t "$TEST_NAME"
|
||||
|
||||
### Utilities
|
||||
```bash
|
||||
pnpm run format # Format code across entire project
|
||||
pnpm run nuke # Remove all node_modules, build files, wipe database, docker containers. **USE WITH CAUTION**
|
||||
```
|
||||
|
||||
@@ -152,6 +154,8 @@ pnpm run nuke # Remove all node_modules, build files, wipe database
|
||||
- Jest for API tests, Playwright for E2E tests
|
||||
- For backend/API changes, tests must pass before pushes
|
||||
- Add tests for new API endpoints and features
|
||||
- When writing tests, focus on decoupling each `it` or `test` block to ensure that they can run independently and concurrently. Tests must never depend on the action or outcome of previous or subsequent tests.
|
||||
- When writing tests, especially in the __tests__/async directory, ensure that you avoid `pruneDatabase` calls.
|
||||
|
||||
### Code Conventions
|
||||
- **Pages Router** (not App Router)
|
||||
@@ -186,3 +190,9 @@ To get a project, use the `get_project` capability with the full project name as
|
||||
|
||||
## TypeScript Best Practices
|
||||
- In TypeScript, if possible, don't use the `any` type
|
||||
|
||||
## General Coding Guidelines
|
||||
- For easier code reviews, prefer not to move functions etc around within a file unless necessary or instructed to do so
|
||||
|
||||
## Development Tips
|
||||
- Before trying to build the package, try running the linter once first
|
||||
+39
-6
@@ -124,16 +124,23 @@ Requirements
|
||||
cd langfuse
|
||||
```
|
||||
|
||||
3. Create an env file
|
||||
3. Install dependencies and set up pre-commit hooks
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm run prepare # Sets up Husky pre-commit hooks for code formatting
|
||||
```
|
||||
|
||||
4. Create an env file
|
||||
|
||||
```bash
|
||||
cp .env.dev.example .env
|
||||
```
|
||||
|
||||
4. Run the entire infrastructure in dev mode. **Note**: if you have an existing database, this command wipes it.
|
||||
4. Run the entire infrastructure in dev mode. **Note**: if you have an existing database, this command wipes it. Also, this will fail on the very first run. Please run it again.
|
||||
|
||||
```bash
|
||||
pnpm run dx # first run only (resets db, node_modules, ...)
|
||||
pnpm run dx # first run only (resets db, docker containers, etc...)
|
||||
pnpm run dev # any subsequent runs
|
||||
```
|
||||
|
||||
@@ -149,8 +156,8 @@ Requirements
|
||||
- Username: `demo@langfuse.com`
|
||||
- Password: `password`
|
||||
|
||||
|
||||
To get comprehensive example data, you can use the `seed` command:
|
||||
|
||||
```sh
|
||||
pnpm run db:seed:examples
|
||||
```
|
||||
@@ -209,31 +216,57 @@ On the main branch, we adhere to the best practices of [conventional commits](ht
|
||||
All tests run in the CI and must pass before merging.
|
||||
All tests run against a running langfuse instance and **write/delete real data from the database**.
|
||||
|
||||
### Test Database Setup
|
||||
|
||||
Per default, the tests use the local development database. Therefore, wiping your data in the process.
|
||||
For proper test isolation, create a `.env.test` file in the root directory:
|
||||
|
||||
```bash
|
||||
cp .env.test.example .env.test
|
||||
```
|
||||
|
||||
Then, a different PostgreSQL and Redis are used for the tests.
|
||||
The `.env.test` file only overrides the set values and falls back on `.env` for all undefined values.
|
||||
|
||||
- **PostgreSQL**: Uses separate `langfuse_test` database for isolation
|
||||
- **ClickHouse**: Uses shared `default` database for now
|
||||
- **Redis**: Uses database 1 instead of 0 for isolation (Redis data is not cleaned between tests)
|
||||
|
||||
Tests automatically create the PostgreSQL test database if it doesn't exist and clean up data between runs.
|
||||
|
||||
### Tests in the `web` package (public API)
|
||||
We're using Jest with in the `web` package. Therefore, if you want to provide an argument to the test runner, do it directly without an intermittent ` -- `.
|
||||
|
||||
We're using Jest with in the `web` package. Therefore, if you want to provide an argument to the test runner, do it directly without an intermittent `--`.
|
||||
|
||||
There are three types of unit tests:
|
||||
|
||||
- `test-sync`
|
||||
- `test-async`
|
||||
- `test` (for async folder tests)
|
||||
- `test-client`
|
||||
|
||||
To run a specific test, for example the test: `"should handle special characters in prompt names"` in `prompts.v2.servertest.ts`, run:
|
||||
|
||||
```sh
|
||||
cd web # or with --filter=web
|
||||
pnpm test-sync --testPathPattern="prompts\.v2\.servertest" --testNamePattern="should handle special characters in prompt names"
|
||||
# for async folder tests:
|
||||
pnpm test -- --testPathPattern="observations-api" --testNamePattern="should fetch all observations"
|
||||
```
|
||||
|
||||
To run all tests:
|
||||
|
||||
```sh
|
||||
pnpm run test
|
||||
```
|
||||
|
||||
Run interactively in watch mode (not recommended!)
|
||||
|
||||
```sh
|
||||
pnpm run test:watch
|
||||
```
|
||||
|
||||
### Tests in the `worker` package
|
||||
|
||||
For the `worker` package, we're using `vitest` to run unit tests.
|
||||
|
||||
```sh
|
||||
|
||||
+6
-5
@@ -94,7 +94,7 @@ Langfuse 是一个 **开源 LLM 工程** 平台。它帮助团队协作 **开发
|
||||
|
||||
- [提示管理](https://langfuse.com/docs/prompts/get-started) 帮助你集中管理、版本控制并协作迭代提示。得益于服务器和客户端的高效缓存,你可以在不增加延迟的情况下反复迭代提示。
|
||||
|
||||
- [评估](https://langfuse.com/docs/scores/overview) 是 LLM 应用开发流程的关键组成部分,Langfuse 能够满足你的多样需求。它支持 LLM 作为“裁判”、用户反馈收集、手动标注以及通过 API/SDK 实现自定义评估流程。
|
||||
- [评估](https://langfuse.com/docs/scores/overview) 是 LLM 应用开发流程的关键组成部分,Langfuse 能够满足你的多样需求。它支持 LLM 作为"裁判"、用户反馈收集、手动标注以及通过 API/SDK 实现自定义评估流程。
|
||||
|
||||
- [数据集](https://langfuse.com/docs/datasets/overview) 为评估你的 LLM 应用提供测试集和基准。它们支持持续改进、部署前测试、结构化实验、灵活评估,并能与 LangChain、LlamaIndex 等框架无缝整合。
|
||||
|
||||
@@ -135,7 +135,7 @@ Langfuse 是一个 **开源 LLM 工程** 平台。它帮助团队协作 **开发
|
||||
|
||||
- [虚拟机](https://langfuse.com/self-hosting/docker-compose):使用 Docker Compose 在单台虚拟机上部署 Langfuse。
|
||||
|
||||
- 【计划中】:针对各云平台的部署指南,欢迎在以下讨论中投票和评论:[AWS](https://github.com/orgs/langfuse/discussions/4645)、[Google Cloud](https://github.com/langfuse/discussions/4646)、[Azure](https://github.com/orgs/langfuse/discussions/4647)。
|
||||
- Terraform 模板: [AWS](https://langfuse.com/self-hosting/aws)、[Azure](https://langfuse.com/self-hosting/azure)、[GCP](https://langfuse.com/self-hosting/gcp)
|
||||
|
||||
请参阅 [自托管文档](https://langfuse.com/self-hosting) 了解更多关于架构和配置选项的信息。
|
||||
|
||||
@@ -155,6 +155,7 @@ Langfuse 是一个 **开源 LLM 工程** 平台。它帮助团队协作 **开发
|
||||
| [LiteLLM](https://langfuse.com/docs/integrations/litellm) | Python, JS/TS (仅代理) | 允许使用任何 LLM 替代 GPT。支持 Azure、OpenAI、Cohere、Anthropic、Ollama、VLLM、Sagemaker、HuggingFace、Replicate(100+ LLMs)。 |
|
||||
| [Vercel AI SDK](https://langfuse.com/docs/integrations/vercel-ai-sdk) | JS/TS | 基于 TypeScript 的工具包,帮助开发者使用 React、Next.js、Vue、Svelte 和 Node.js 构建 AI 驱动的应用。 |
|
||||
| [API](https://langfuse.com/docs/api) | | 直接调用公共 API。提供 OpenAPI 规格。 |
|
||||
| [Google VertexAI 和 Gemini](https://langfuse.com/docs/integrations/google-vertex-ai) | 模型 | 在 Google 上运行基础模型和微调模型。 |
|
||||
|
||||
### 与 Langfuse 集成的软件包:
|
||||
|
||||
@@ -162,9 +163,9 @@ Langfuse 是一个 **开源 LLM 工程** 平台。它帮助团队协作 **开发
|
||||
| ---------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| [Instructor](https://langfuse.com/docs/integrations/instructor) | 库 | 用于获取结构化 LLM 输出(JSON、Pydantic)的库。 |
|
||||
| [DSPy](https://langfuse.com/docs/integrations/dspy) | 库 | 一个系统性优化语言模型提示和权重的框架。 |
|
||||
| [Amazon Bedrock](https://langfuse.com/docs/integrations/amazon-bedrock) | 模型 | 在 AWS 上运行基础模型和微调模型。 |
|
||||
| [Mirascope](https://langfuse.com/docs/integrations/mirascope) | 库 | 构建 LLM 应用的 Python 工具包。 |
|
||||
| [Ollama](https://langfuse.com/docs/integrations/ollama) | 模型(本地) | 在你的机器上轻松运行开源 LLM。 |
|
||||
| [Amazon Bedrock](https://langfuse.com/docs/integrations/amazon-bedrock) | 模型 | 在 AWS 上运行基础模型和微调模型。 |
|
||||
| [AutoGen](https://langfuse.com/docs/integrations/autogen) | 代理框架 | 用于构建分布式代理的开源 LLM 平台。 |
|
||||
| [Flowise](https://langfuse.com/docs/integrations/flowise) | 聊天/代理界面 | 基于 JS/TS 的无代码构建器,用于定制化 LLM 流程。 |
|
||||
| [Langflow](https://langfuse.com/docs/integrations/langflow) | 聊天/代理界面 | 基于 Python 的 LangChain 用户界面,采用 react-flow 设计,提供便捷的实验与原型构建体验。 |
|
||||
@@ -253,7 +254,7 @@ _[Langfuse 中的公共示例追踪](https://cloud.langfuse.com/project/cloramnk
|
||||
|
||||
- 我们的 [文档](https://langfuse.com/docs) 是查找答案的最佳起点。内容全面,我们投入大量时间进行维护。你也可以通过 GitHub 提出文档修改建议。
|
||||
- [Langfuse 常见问题](https://langfuse.com/faq) 解答了最常见的问题。
|
||||
- 使用 “[Ask AI](https://langfuse.com/docs/ask-ai)” 立即获取问题答案。
|
||||
- 使用 "Ask AI" 立即获取问题答案。
|
||||
|
||||
支持渠道:
|
||||
|
||||
@@ -351,4 +352,4 @@ _[Langfuse 中的公共示例追踪](https://cloud.langfuse.com/project/cloramnk
|
||||
所有数据均不会与第三方共享,也不包含任何敏感信息。我们对这一过程保持高度透明,你可以在 [此处](/web/src/features/telemetry/index.ts) 查看我们收集的具体数据。
|
||||
|
||||
你可以通过设置 `TELEMETRY_ENABLED=false` 来选择退出。
|
||||
````
|
||||
```
|
||||
|
||||
+1
-3
@@ -141,9 +141,7 @@ Langfuseチームによるマネージドデプロイメント。充実した無
|
||||
- **[VM](https://langfuse.com/self-hosting/docker-compose):**
|
||||
Docker Composeを使用して、単一の仮想マシン上でLangfuseを実行します。
|
||||
|
||||
- **Planned:**
|
||||
クラウド固有のデプロイガイドは計画中です。以下のスレッドに対して投票やコメントをお願いします:
|
||||
[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).
|
||||
- Terraform テンプレート: [AWS](https://langfuse.com/self-hosting/aws), [Azure](https://langfuse.com/self-hosting/azure), [GCP](https://langfuse.com/self-hosting/gcp)
|
||||
|
||||
[セルフホスティングのドキュメント](https://langfuse.com/self-hosting)を参照し、アーキテクチャや設定オプションの詳細をご確認ください。
|
||||
|
||||
|
||||
+2
-1
@@ -128,7 +128,7 @@ Langfuse 팀이 관리하는 배포 방식으로, 후한 무료 플랜(취미
|
||||
|
||||
- [Kubernetes (Helm)](https://langfuse.com/self-hosting/kubernetes-helm): Helm을 사용해 Kubernetes 클러스터에서 Langfuse를 실행합니다. 이는 권장되는 프로덕션 배포 방식입니다.
|
||||
- [VM](https://langfuse.com/self-hosting/docker-compose): Docker Compose를 사용해 단일 가상 머신에서 Langfuse를 실행합니다.
|
||||
- 예정: 클라우드별 배포 가이드 – 아래 스레드에서 투표 및 댓글을 남겨주세요: [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).
|
||||
- Terraform 템플릿: [AWS](https://langfuse.com/self-hosting/aws), [Azure](https://langfuse.com/self-hosting/azure), [GCP](https://langfuse.com/self-hosting/gcp)
|
||||
|
||||
자세한 내용은 [자체 호스팅 문서](https://langfuse.com/self-hosting)를 참조하세요.
|
||||
|
||||
@@ -158,6 +158,7 @@ Langfuse 팀이 관리하는 배포 방식으로, 후한 무료 플랜(취미
|
||||
| [Mirascope](https://langfuse.com/docs/integrations/mirascope) | 라이브러리 | LLM 애플리케이션 구축을 위한 Python 툴킷입니다. |
|
||||
| [Ollama](https://langfuse.com/docs/integrations/ollama) | 모델 (로컬) | 자신의 컴퓨터에서 오픈 소스 LLM을 손쉽게 실행할 수 있습니다. |
|
||||
| [Amazon Bedrock](https://langfuse.com/docs/integrations/amazon-bedrock) | 모델 | AWS에서 기본 및 파인튜닝된 모델을 실행합니다. |
|
||||
| [Google VertexAI and Gemini](https://langfuse.com/docs/integrations/google-vertex-ai) | 모델 | Google에서 기본 및 파인튜닝된 모델을 실행합니다. |
|
||||
| [AutoGen](https://langfuse.com/docs/integrations/autogen) | 에이전트 프레임워크 | 분산 에이전트 구축을 위한 오픈 소스 LLM 플랫폼입니다. |
|
||||
| [Flowise](https://langfuse.com/docs/integrations/flowise) | 채팅/에이전트 UI | 맞춤형 LLM 플로우를 위한 JS/TS 코드 없는(no-code) 빌더입니다. |
|
||||
| [Langflow](https://langfuse.com/docs/integrations/langflow) | 채팅/에이전트 UI | react-flow를 활용하여 실험 및 프로토타이핑을 손쉽게 할 수 있도록 디자인된 LangChain용 Python 기반 UI입니다. |
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Code Review Instructions
|
||||
|
||||
## Database Migrations
|
||||
|
||||
### ClickHouse
|
||||
|
||||
- ClickHouse migrations in the `packages/shared/clickhouse/migrations/clustered` directory should include `ON CLUSTER default` and should use `Replicated` merge tree table types.
|
||||
- E.g. `ReplacingMergeTree` is likely an error while `ReplicatedReplacingMergeTree` would be correct in most cases.
|
||||
- ClickHouse migrations in the `packages/shared/clickhouse/migrations/unclustered` directory must not include `ON CLUSTER` statements and must not use `Replicated` merge tree table types.
|
||||
- Migrations in `packages/shared/clickhouse/migrations/clustered` should match their counterparts in `packages/shared/clickhouse/migrations/unclustered` aside from the restrictions listed above.
|
||||
|
||||
### Postgres
|
||||
|
||||
- Most `schema.prisma` changes should produce a change in `packages/shared/prisma/migrations`.
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server
|
||||
image: docker.io/clickhouse/clickhouse-server
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
@@ -98,7 +98,7 @@ services:
|
||||
start_period: 1s
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
image: docker.io/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'
|
||||
@@ -118,7 +118,7 @@ services:
|
||||
start_period: 1s
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
image: docker.io/redis:7
|
||||
restart: always
|
||||
command: >
|
||||
--requirepass ${REDIS_AUTH:-myredissecret}
|
||||
@@ -131,7 +131,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:24.3
|
||||
image: docker.io/clickhouse/clickhouse-server:24.3
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
- langfuse_azurite_data:/data
|
||||
|
||||
redis:
|
||||
image: redis:7.2.4
|
||||
image: docker.io/redis:7.2.4
|
||||
restart: always
|
||||
command: >
|
||||
--requirepass ${REDIS_AUTH:-myredissecret}
|
||||
@@ -32,7 +32,7 @@ services:
|
||||
- 6379:6379
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:24.3
|
||||
image: docker.io/clickhouse/clickhouse-server:24.3
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
- postgres
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
image: docker.io/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'
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
start_period: 1s
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:24.3
|
||||
image: docker.io/clickhouse/clickhouse-server:24.3
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
- postgres
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
image: docker.io/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'
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
start_period: 1s
|
||||
|
||||
redis:
|
||||
image: redis:7.2.4
|
||||
image: docker.io/redis:7.2.4
|
||||
restart: always
|
||||
command: >
|
||||
--requirepass ${REDIS_AUTH:-myredissecret}
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
- 127.0.0.1:6379:6379
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
|
||||
+9
-7
@@ -5,7 +5,7 @@
|
||||
# External connections from other machines will not be able to reach these services directly.
|
||||
services:
|
||||
langfuse-worker:
|
||||
image: langfuse/langfuse-worker:3
|
||||
image: docker.io/langfuse/langfuse-worker:3
|
||||
restart: always
|
||||
depends_on: &langfuse-depends-on
|
||||
postgres:
|
||||
@@ -19,6 +19,7 @@ services:
|
||||
ports:
|
||||
- 127.0.0.1:3030:3030
|
||||
environment: &langfuse-worker-env
|
||||
NEXTAUTH_URL: http://localhost:3000
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres # CHANGEME
|
||||
SALT: "mysalt" # CHANGEME
|
||||
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # CHANGEME: generate via `openssl rand -hex 32`
|
||||
@@ -62,16 +63,17 @@ services:
|
||||
REDIS_TLS_CA: ${REDIS_TLS_CA:-/certs/ca.crt}
|
||||
REDIS_TLS_CERT: ${REDIS_TLS_CERT:-/certs/redis.crt}
|
||||
REDIS_TLS_KEY: ${REDIS_TLS_KEY:-/certs/redis.key}
|
||||
EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-}
|
||||
SMTP_CONNECTION_URL: ${SMTP_CONNECTION_URL:-}
|
||||
|
||||
langfuse-web:
|
||||
image: langfuse/langfuse:3
|
||||
image: docker.io/langfuse/langfuse:3
|
||||
restart: always
|
||||
depends_on: *langfuse-depends-on
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
<<: *langfuse-worker-env
|
||||
NEXTAUTH_URL: http://localhost:3000
|
||||
NEXTAUTH_SECRET: mysecret # CHANGEME
|
||||
LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
|
||||
LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-}
|
||||
@@ -84,7 +86,7 @@ services:
|
||||
LANGFUSE_INIT_USER_PASSWORD: ${LANGFUSE_INIT_USER_PASSWORD:-}
|
||||
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server
|
||||
image: docker.io/clickhouse/clickhouse-server
|
||||
restart: always
|
||||
user: "101:101"
|
||||
environment:
|
||||
@@ -105,7 +107,7 @@ services:
|
||||
start_period: 1s
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
image: docker.io/minio/minio
|
||||
restart: always
|
||||
entrypoint: sh
|
||||
# create the 'langfuse' bucket before starting the service
|
||||
@@ -126,7 +128,7 @@ services:
|
||||
start_period: 1s
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
image: docker.io/redis:7
|
||||
restart: always
|
||||
# CHANGEME: row below to secure redis password
|
||||
command: >
|
||||
@@ -140,7 +142,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
|
||||
+1
-7
@@ -26,7 +26,6 @@
|
||||
"dependencies": {
|
||||
"@langfuse/shared": "workspace:*",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"axios": "^1.8.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"next": "^14.2.30",
|
||||
"next-auth": "^4.24.11",
|
||||
@@ -41,14 +40,9 @@
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.2.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"nanoid": "^3.3.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@ service:
|
||||
docs: limit of items per page
|
||||
response: PaginatedAnnotationQueues
|
||||
|
||||
createQueue:
|
||||
docs: Create an annotation queue
|
||||
method: POST
|
||||
path: /annotation-queues
|
||||
request: CreateAnnotationQueueRequest
|
||||
response: AnnotationQueue
|
||||
|
||||
getQueue:
|
||||
docs: Get an annotation queue by ID
|
||||
method: GET
|
||||
@@ -105,6 +112,28 @@ service:
|
||||
docs: The unique identifier of the annotation queue item
|
||||
response: DeleteAnnotationQueueItemResponse
|
||||
|
||||
createQueueAssignment:
|
||||
docs: Create an assignment for a user to an annotation queue
|
||||
method: POST
|
||||
path: /annotation-queues/{queueId}/assignments
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
request: AnnotationQueueAssignmentRequest
|
||||
response: CreateAnnotationQueueAssignmentResponse
|
||||
|
||||
deleteQueueAssignment:
|
||||
docs: Delete an assignment for a user to an annotation queue
|
||||
method: DELETE
|
||||
path: /annotation-queues/{queueId}/assignments
|
||||
path-parameters:
|
||||
queueId:
|
||||
type: string
|
||||
docs: The unique identifier of the annotation queue
|
||||
request: AnnotationQueueAssignmentRequest
|
||||
response: DeleteAnnotationQueueAssignmentResponse
|
||||
|
||||
types:
|
||||
AnnotationQueueStatus:
|
||||
enum:
|
||||
@@ -146,6 +175,12 @@ types:
|
||||
data: list<AnnotationQueueItem>
|
||||
meta: pagination.MetaResponse
|
||||
|
||||
CreateAnnotationQueueRequest:
|
||||
properties:
|
||||
name: string
|
||||
description: optional<string>
|
||||
scoreConfigIds: list<string>
|
||||
|
||||
CreateAnnotationQueueItemRequest:
|
||||
properties:
|
||||
objectId: string
|
||||
@@ -163,3 +198,17 @@ types:
|
||||
properties:
|
||||
success: boolean
|
||||
message: string
|
||||
|
||||
AnnotationQueueAssignmentRequest:
|
||||
properties:
|
||||
userId: string
|
||||
|
||||
DeleteAnnotationQueueAssignmentResponse:
|
||||
properties:
|
||||
success: boolean
|
||||
|
||||
CreateAnnotationQueueAssignmentResponse:
|
||||
properties:
|
||||
userId: string
|
||||
queueId: string
|
||||
projectId: string
|
||||
|
||||
@@ -27,7 +27,7 @@ service:
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
response: PaginatedDatasetRunItems
|
||||
response: PaginatedDatasetRunItems
|
||||
|
||||
types:
|
||||
CreateDatasetRunItemRequest:
|
||||
|
||||
@@ -145,6 +145,13 @@ types:
|
||||
- SPAN
|
||||
- GENERATION
|
||||
- EVENT
|
||||
- AGENT
|
||||
- TOOL
|
||||
- CHAIN
|
||||
- RETRIEVER
|
||||
- EVALUATOR
|
||||
- EMBEDDING
|
||||
- GUARDRAIL
|
||||
|
||||
IngestionUsage:
|
||||
discriminated: false
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
pagination: ./utils/pagination.yml
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public
|
||||
endpoints:
|
||||
list:
|
||||
method: GET
|
||||
docs: Get all LLM connections in a project
|
||||
path: /llm-connections
|
||||
request:
|
||||
name: GetLlmConnectionsRequest
|
||||
query-parameters:
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
response: PaginatedLlmConnections
|
||||
upsert:
|
||||
method: PUT
|
||||
docs: Create or update an LLM connection. The connection is upserted on provider.
|
||||
path: /llm-connections
|
||||
request: UpsertLlmConnectionRequest
|
||||
response: LlmConnection
|
||||
|
||||
types:
|
||||
LlmConnection:
|
||||
docs: LLM API connection configuration (secrets excluded)
|
||||
properties:
|
||||
id: string
|
||||
provider:
|
||||
type: string
|
||||
docs: Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting.
|
||||
adapter:
|
||||
type: string
|
||||
docs: The adapter used to interface with the LLM
|
||||
displaySecretKey:
|
||||
type: string
|
||||
docs: Masked version of the secret key for display purposes
|
||||
baseURL:
|
||||
type: optional<string>
|
||||
docs: Custom base URL for the LLM API
|
||||
customModels:
|
||||
type: list<string>
|
||||
docs: List of custom model names available for this connection
|
||||
withDefaultModels:
|
||||
type: boolean
|
||||
docs: Whether to include default models for this adapter
|
||||
extraHeaderKeys:
|
||||
type: list<string>
|
||||
docs: Keys of extra headers sent with requests (values excluded for security)
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
|
||||
PaginatedLlmConnections:
|
||||
properties:
|
||||
data: list<LlmConnection>
|
||||
meta: pagination.MetaResponse
|
||||
|
||||
UpsertLlmConnectionRequest:
|
||||
docs: Request to create or update an LLM connection (upsert)
|
||||
properties:
|
||||
provider:
|
||||
type: string
|
||||
docs: Provider name (e.g., 'openai', 'my-gateway'). Must be unique in project, used for upserting.
|
||||
adapter:
|
||||
type: LlmAdapter
|
||||
docs: The adapter used to interface with the LLM
|
||||
secretKey:
|
||||
type: string
|
||||
docs: Secret key for the LLM API.
|
||||
baseURL:
|
||||
type: optional<string>
|
||||
docs: Custom base URL for the LLM API
|
||||
customModels:
|
||||
type: optional<list<string>>
|
||||
docs: List of custom model names
|
||||
withDefaultModels:
|
||||
type: optional<boolean>
|
||||
docs: Whether to include default models. Default is true.
|
||||
extraHeaders:
|
||||
type: optional<map<string, string>>
|
||||
docs: Extra headers to send with requests
|
||||
|
||||
LlmAdapter:
|
||||
enum:
|
||||
- value: anthropic
|
||||
name: Anthropic
|
||||
- value: openai
|
||||
name: OpenAI
|
||||
- value: azure
|
||||
name: Azure
|
||||
- value: bedrock
|
||||
name: Bedrock
|
||||
- value: google-vertex-ai
|
||||
name: GoogleVertexAI
|
||||
- value: google-ai-studio
|
||||
name: GoogleAIStudio
|
||||
@@ -32,6 +32,9 @@ service:
|
||||
userId: optional<string>
|
||||
type: optional<string>
|
||||
traceId: optional<string>
|
||||
level:
|
||||
type: optional<commons.ObservationLevel>
|
||||
docs: Optional filter for observations with a specific level (e.g. "DEBUG", "DEFAULT", "WARNING", "ERROR").
|
||||
parentObservationId: optional<string>
|
||||
environment:
|
||||
type: optional<string>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json
|
||||
default-group: local
|
||||
groups:
|
||||
local:
|
||||
@@ -7,6 +8,7 @@ groups:
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../web/public/generated/api
|
||||
|
||||
- name: fernapi/fern-python-sdk
|
||||
version: 2.16.0
|
||||
output:
|
||||
@@ -19,35 +21,32 @@ groups:
|
||||
pydantic_config:
|
||||
require_optional_fields: false
|
||||
use_str_enums: false
|
||||
# - name: fernapi/fern-java-sdk
|
||||
# version: 2.20.1
|
||||
# output:
|
||||
# location: local-file-system
|
||||
# path: ../../../../langfuse-java/src/main/java/com/langfuse/client/
|
||||
# config:
|
||||
# client-class-name: LangfuseClient
|
||||
|
||||
# - name: fernapi/fern-java-sdk
|
||||
# version: 2.20.1
|
||||
# output:
|
||||
# location: local-file-system
|
||||
# path: ../../../../langfuse-java/src/main/java/com/langfuse/client/
|
||||
# config:
|
||||
# client-class-name: LangfuseClient
|
||||
|
||||
- name: fernapi/fern-typescript-node-sdk
|
||||
version: 2.6.1
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../generated/typescript
|
||||
config:
|
||||
namespaceExport: LangfuseAPI
|
||||
outputSourceFiles: true
|
||||
skipResponseValidation: true
|
||||
fetchSupport: native
|
||||
formDataSupport: Node18
|
||||
fileResponseType: binary-response
|
||||
streamType: web
|
||||
omitFernHeaders: true
|
||||
|
||||
- name: fernapi/fern-postman
|
||||
version: 0.0.45
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../web/public/generated/postman
|
||||
# published:
|
||||
# generators:
|
||||
# - name: fernapi/fern-python-sdk
|
||||
# version: 0.3.7
|
||||
# output:
|
||||
# location: pypi
|
||||
# url: pypi.buildwithfern.com
|
||||
# package-name: finto-fern-langfuse
|
||||
# config:
|
||||
# namespaceExport: Langfuse
|
||||
# allowCustomFetcher: true
|
||||
# - name: fernapi/fern-typescript-node-sdk
|
||||
# version: 0.7.1
|
||||
# output:
|
||||
# location: npm
|
||||
# url: npm.buildwithfern.com
|
||||
# package-name: "@finto-fern/langfuse-node"
|
||||
# config:
|
||||
# namespaceExport: Langfuse
|
||||
# allowCustomFetcher: true
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
python/
|
||||
+10
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langfuse",
|
||||
"version": "3.78.1",
|
||||
"version": "3.98.2",
|
||||
"author": "engineering@langfuse.com",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@@ -17,9 +17,9 @@
|
||||
"db:seed": "turbo run db:seed",
|
||||
"db:seed:examples": "turbo run db:seed:examples",
|
||||
"nuke": "bash ./scripts/nuke.sh",
|
||||
"dx": "pnpm i && pnpm run infra:dev: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 && 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",
|
||||
"dx": "pnpm i && pnpm run infra:dev:prune && pnpm run infra:dev:up --pull always && pnpm --filter=shared run db:reset:test && 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:test && 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:test && 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",
|
||||
"dev": "turbo run dev",
|
||||
@@ -27,6 +27,8 @@
|
||||
"dev:web": "turbo run dev --filter=web",
|
||||
"dev:web-turbo": "turbo run dev --filter=web -- --turbo",
|
||||
"lint": "turbo run lint",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,css}\" --experimental-cli",
|
||||
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,css}\" --experimental-cli",
|
||||
"test": "turbo run test",
|
||||
"release": "dotenv -e ../.env -- release-it",
|
||||
"prepare": "husky"
|
||||
@@ -36,9 +38,9 @@
|
||||
"braces": "3.0.3",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"husky": "^9.0.11",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.6.2",
|
||||
"release-it": "^19.0.3",
|
||||
"turbo": "^2.5.4"
|
||||
"turbo": "^2.5.5"
|
||||
},
|
||||
"release-it": {
|
||||
"git": {
|
||||
@@ -89,7 +91,8 @@
|
||||
"nanoid": "^3.3.8",
|
||||
"katex": "^0.16.21",
|
||||
"tar-fs": "^2.1.2",
|
||||
"rollup@^4.0.0": "^4.22.4"
|
||||
"rollup@^4.0.0": "^4.22.4",
|
||||
"@types/node-fetch": "^2.6.13"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"next-auth@4.24.11": "patches/next-auth@4.24.11.patch"
|
||||
|
||||
@@ -35,5 +35,13 @@ module.exports = {
|
||||
{
|
||||
files: ["*.js?(x)", "*.ts?(x)"],
|
||||
},
|
||||
{
|
||||
files: ["*.ts", "*.mts", "*.cts", "*.tsx"],
|
||||
// no-undef doesn't make sense in TS, see:
|
||||
// https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
rules: {
|
||||
"no-undef": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@vercel/style-guide": "^6.0.0",
|
||||
"eslint-config-next": "^14.2.15",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "^2.5.4",
|
||||
"eslint-config-turbo": "^2.5.5",
|
||||
"eslint-plugin-only-warn": "^1.1.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE dataset_run_items ON CLUSTER default;
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE dataset_run_items ON CLUSTER default (
|
||||
-- primary identifiers
|
||||
`id` String,
|
||||
`project_id` String,
|
||||
`dataset_run_id` String,
|
||||
`dataset_item_id` String,
|
||||
`dataset_id` String,
|
||||
`trace_id` String,
|
||||
`observation_id` Nullable(String),
|
||||
|
||||
-- error field
|
||||
`error` Nullable(String),
|
||||
|
||||
-- timestamps
|
||||
`created_at` DateTime64(3) DEFAULT now(),
|
||||
`updated_at` DateTime64(3) DEFAULT now(),
|
||||
|
||||
-- denormalized immutable dataset run fields
|
||||
`dataset_run_name` String,
|
||||
`dataset_run_description` Nullable(String),
|
||||
`dataset_run_metadata` Map(LowCardinality(String), String),
|
||||
`dataset_run_created_at` DateTime64(3),
|
||||
|
||||
-- denormalized dataset item fields (mutable, but snapshots are relevant)
|
||||
`dataset_item_input` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_expected_output` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_metadata` Map(LowCardinality(String), String),
|
||||
|
||||
-- clickhouse engine fields
|
||||
`event_ts` DateTime64(3),
|
||||
`is_deleted` UInt8,
|
||||
|
||||
-- For dataset item lookups
|
||||
INDEX idx_dataset_item dataset_item_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
) ENGINE = ReplacingMergeTree(event_ts, is_deleted)
|
||||
ORDER BY (project_id, dataset_id, dataset_run_id, id);
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
-- Drop materialized views first
|
||||
DROP VIEW IF EXISTS traces_30d_amt_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS traces_7d_amt_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS traces_all_amt_mv ON CLUSTER default;
|
||||
|
||||
-- Drop AMT tables
|
||||
DROP TABLE IF EXISTS traces_30d_amt ON CLUSTER default;
|
||||
DROP TABLE IF EXISTS traces_7d_amt ON CLUSTER default;
|
||||
DROP TABLE IF EXISTS traces_all_amt ON CLUSTER default;
|
||||
|
||||
-- Drop the Null table
|
||||
DROP TABLE IF EXISTS traces_null ON CLUSTER default;
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
-- Create a Null table that serves as a trigger for all materialized views.
|
||||
-- We use a Null engine here to avoid storing intermediate results and save on storage.
|
||||
CREATE TABLE traces_null ON CLUSTER default
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`start_time` DateTime64(3),
|
||||
`end_time` Nullable(DateTime64(3)),
|
||||
`name` Nullable(String),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` Map(LowCardinality(String), String),
|
||||
`user_id` Nullable(String),
|
||||
`session_id` Nullable(String),
|
||||
`environment` String,
|
||||
`tags` Array(String),
|
||||
`version` Nullable(String),
|
||||
`release` Nullable(String),
|
||||
|
||||
-- UI properties - We make them nullable to prevent absent values being interpreted as overwrites.
|
||||
`bookmarked` Nullable(Bool),
|
||||
`public` Nullable(Bool),
|
||||
|
||||
-- Aggregations -- DO NOT USE
|
||||
`observation_ids` Array(String),
|
||||
`score_ids` Array(String),
|
||||
`cost_details` Map(String, Decimal64(12)),
|
||||
`usage_details` Map(String, UInt64),
|
||||
-- TODO: Do we want to aggregate/collect `levels` seen within the trace?
|
||||
|
||||
-- Input/Output
|
||||
`input` String,
|
||||
`output` String,
|
||||
|
||||
`created_at` DateTime64(3),
|
||||
`updated_at` DateTime64(3),
|
||||
`event_ts` DateTime64(3)
|
||||
) Engine = Null();
|
||||
|
||||
-- Create the all AMT
|
||||
CREATE TABLE traces_all_amt ON CLUSTER default
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`timestamp` SimpleAggregateFunction(min, DateTime64(3)), -- Backward compatibility: redundant with start_time
|
||||
`start_time` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`end_time` SimpleAggregateFunction(max, Nullable(DateTime64(3))),
|
||||
`name` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` SimpleAggregateFunction(maxMap, Map(String, String)),
|
||||
`user_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`session_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`environment` SimpleAggregateFunction(anyLast, String),
|
||||
`tags` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`version` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`release` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- UI properties
|
||||
`bookmarked` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
`public` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
|
||||
-- Aggregations -- DO NOT USE
|
||||
`observation_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`score_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`cost_details` SimpleAggregateFunction(sumMap, Map(String, Decimal(38, 12))),
|
||||
`usage_details` SimpleAggregateFunction(sumMap, Map(String, UInt64)),
|
||||
|
||||
-- Input/Output -> prefer correctness via argMax
|
||||
`input` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
`output` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
|
||||
`created_at` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`updated_at` SimpleAggregateFunction(max, DateTime64(3)),
|
||||
|
||||
-- Indexes
|
||||
INDEX idx_trace_id id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_user_id user_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_session_id session_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_name name TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_version version TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_release release TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_tags tags TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) Engine = AggregatingMergeTree()
|
||||
ORDER BY (project_id, id);
|
||||
|
||||
-- Create materialized view for all_amt
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS traces_all_amt_mv ON CLUSTER default TO traces_all_amt AS
|
||||
SELECT
|
||||
-- Identifiers
|
||||
tn.project_id as project_id,
|
||||
tn.id as id,
|
||||
min(tn.start_time) as timestamp, -- Backward compatibility: redundant with start_time
|
||||
min(tn.start_time) as start_time,
|
||||
max(coalesce(tn.end_time, tn.start_time)) as end_time,
|
||||
anyLast(tn.name) as name,
|
||||
|
||||
-- Metadata properties
|
||||
maxMap(tn.metadata) as metadata,
|
||||
anyLast(tn.user_id) as user_id,
|
||||
anyLast(tn.session_id) as session_id,
|
||||
anyLast(tn.environment) as environment,
|
||||
groupUniqArrayArray(tn.tags) as tags,
|
||||
anyLast(tn.version) as version,
|
||||
anyLast(tn.release) as release,
|
||||
|
||||
-- UI properties
|
||||
argMaxState(tn.bookmarked, if(tn.bookmarked is not null, tn.event_ts, toDateTime64(0, 3))) as bookmarked,
|
||||
argMaxState(tn.public, if(tn.public is not null, tn.event_ts, toDateTime64(0, 3))) as public,
|
||||
|
||||
-- Aggregations -- DO NOT USE
|
||||
groupUniqArrayArray(tn.observation_ids) as observation_ids,
|
||||
groupUniqArrayArray(tn.score_ids) as score_ids,
|
||||
sumMap(tn.cost_details) as cost_details,
|
||||
sumMap(tn.usage_details) as usage_details,
|
||||
|
||||
-- Input/Output
|
||||
argMaxState(tn.input, if(tn.input <> '', tn.event_ts, toDateTime64(0, 3))) as input,
|
||||
argMaxState(tn.output, if(tn.output <> '', tn.event_ts, toDateTime64(0, 3))) as output,
|
||||
|
||||
min(tn.created_at) as created_at,
|
||||
max(tn.updated_at) as updated_at
|
||||
FROM traces_null tn
|
||||
GROUP BY project_id, id;
|
||||
|
||||
-- Create the 7-day TTL AMT
|
||||
CREATE TABLE traces_7d_amt ON CLUSTER default
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`timestamp` SimpleAggregateFunction(min, DateTime64(3)), -- Backward compatibility: redundant with start_time
|
||||
`start_time` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`end_time` SimpleAggregateFunction(max, Nullable(DateTime64(3))),
|
||||
`name` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` SimpleAggregateFunction(maxMap, Map(String, String)),
|
||||
`user_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`session_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`environment` SimpleAggregateFunction(anyLast, String),
|
||||
`tags` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`version` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`release` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- UI properties
|
||||
`bookmarked` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
`public` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
|
||||
-- Aggregations -- DO NOT USE
|
||||
`observation_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`score_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`cost_details` SimpleAggregateFunction(sumMap, Map(String, Decimal(38, 12))),
|
||||
`usage_details` SimpleAggregateFunction(sumMap, Map(String, UInt64)),
|
||||
|
||||
-- Input/Output -> prefer correctness via argMax
|
||||
`input` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
`output` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
|
||||
`created_at` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`updated_at` SimpleAggregateFunction(max, DateTime64(3)),
|
||||
|
||||
-- Indexes
|
||||
INDEX idx_user_id user_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_session_id session_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_name name TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_version version TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_release release TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_tags tags TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) Engine = AggregatingMergeTree()
|
||||
ORDER BY (project_id, id)
|
||||
TTL toDate(start_time) + INTERVAL 7 DAY;
|
||||
|
||||
-- Create materialized view for 7d_amt
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS traces_7d_amt_mv ON CLUSTER default TO traces_7d_amt AS
|
||||
SELECT
|
||||
-- Identifiers
|
||||
tn.project_id as project_id,
|
||||
tn.id as id,
|
||||
min(tn.start_time) as timestamp, -- Backward compatibility: redundant with start_time
|
||||
min(tn.start_time) as start_time,
|
||||
max(coalesce(tn.end_time, tn.start_time)) as end_time,
|
||||
anyLast(tn.name) as name,
|
||||
|
||||
-- Metadata properties
|
||||
maxMap(tn.metadata) as metadata,
|
||||
anyLast(tn.user_id) as user_id,
|
||||
anyLast(tn.session_id) as session_id,
|
||||
anyLast(tn.environment) as environment,
|
||||
groupUniqArrayArray(tn.tags) as tags,
|
||||
anyLast(tn.version) as version,
|
||||
anyLast(tn.release) as release,
|
||||
|
||||
-- UI properties
|
||||
argMaxState(tn.bookmarked, if(tn.bookmarked is not null, tn.event_ts, toDateTime64(0, 3))) as bookmarked,
|
||||
argMaxState(tn.public, if(tn.public is not null, tn.event_ts, toDateTime64(0, 3))) as public,
|
||||
|
||||
-- Aggregations -- DO NOT USE
|
||||
groupUniqArrayArray(tn.observation_ids) as observation_ids,
|
||||
groupUniqArrayArray(tn.score_ids) as score_ids,
|
||||
sumMap(tn.cost_details) as cost_details,
|
||||
sumMap(tn.usage_details) as usage_details,
|
||||
|
||||
-- Input/Output
|
||||
argMaxState(tn.input, if(tn.input <> '', tn.event_ts, toDateTime64(0, 3))) as input,
|
||||
argMaxState(tn.output, if(tn.output <> '', tn.event_ts, toDateTime64(0, 3))) as output,
|
||||
|
||||
min(tn.created_at) as created_at,
|
||||
max(tn.updated_at) as updated_at
|
||||
FROM traces_null tn
|
||||
GROUP BY project_id, id;
|
||||
|
||||
-- Create the 30-day TTL AMT
|
||||
CREATE TABLE traces_30d_amt ON CLUSTER default
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`timestamp` SimpleAggregateFunction(min, DateTime64(3)), -- Backward compatibility: redundant with start_time
|
||||
`start_time` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`end_time` SimpleAggregateFunction(max, Nullable(DateTime64(3))),
|
||||
`name` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` SimpleAggregateFunction(maxMap, Map(String, String)),
|
||||
`user_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`session_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`environment` SimpleAggregateFunction(anyLast, String),
|
||||
`tags` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`version` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`release` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- UI properties
|
||||
`bookmarked` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
`public` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
|
||||
-- Aggregations -- DO NOT USE
|
||||
`observation_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`score_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`cost_details` SimpleAggregateFunction(sumMap, Map(String, Decimal(38, 12))),
|
||||
`usage_details` SimpleAggregateFunction(sumMap, Map(String, UInt64)),
|
||||
|
||||
-- Input/Output -> prefer correctness via argMax
|
||||
`input` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
`output` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
|
||||
`created_at` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`updated_at` SimpleAggregateFunction(max, DateTime64(3)),
|
||||
|
||||
-- Indexes
|
||||
INDEX idx_user_id user_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_session_id session_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_name name TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_version version TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_release release TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_tags tags TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) Engine = AggregatingMergeTree()
|
||||
ORDER BY (project_id, id)
|
||||
TTL toDate(start_time) + INTERVAL 30 DAY;
|
||||
|
||||
-- Create materialized view for 30d_amt
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS traces_30d_amt_mv ON CLUSTER default TO traces_30d_amt AS
|
||||
SELECT
|
||||
-- Identifiers
|
||||
tn.project_id as project_id,
|
||||
tn.id as id,
|
||||
min(tn.start_time) as timestamp, -- Backward compatibility: redundant with start_time
|
||||
min(tn.start_time) as start_time,
|
||||
max(coalesce(tn.end_time, tn.start_time)) as end_time,
|
||||
anyLast(tn.name) as name,
|
||||
|
||||
-- Metadata properties
|
||||
maxMap(tn.metadata) as metadata,
|
||||
anyLast(tn.user_id) as user_id,
|
||||
anyLast(tn.session_id) as session_id,
|
||||
anyLast(tn.environment) as environment,
|
||||
groupUniqArrayArray(tn.tags) as tags,
|
||||
anyLast(tn.version) as version,
|
||||
anyLast(tn.release) as release,
|
||||
|
||||
-- UI properties
|
||||
argMaxState(tn.bookmarked, if(tn.bookmarked is not null, tn.event_ts, toDateTime64(0, 3))) as bookmarked,
|
||||
argMaxState(tn.public, if(tn.public is not null, tn.event_ts, toDateTime64(0, 3))) as public,
|
||||
|
||||
-- Aggregations -- DO NOT USE
|
||||
groupUniqArrayArray(tn.observation_ids) as observation_ids,
|
||||
groupUniqArrayArray(tn.score_ids) as score_ids,
|
||||
sumMap(tn.cost_details) as cost_details,
|
||||
sumMap(tn.usage_details) as usage_details,
|
||||
|
||||
-- Input/Output
|
||||
argMaxState(tn.input, if(tn.input <> '', tn.event_ts, toDateTime64(0, 3))) as input,
|
||||
argMaxState(tn.output, if(tn.output <> '', tn.event_ts, toDateTime64(0, 3))) as output,
|
||||
|
||||
min(tn.created_at) as created_at,
|
||||
max(tn.updated_at) as updated_at
|
||||
FROM traces_null tn
|
||||
GROUP BY project_id, id;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE dataset_run_items_rmt ON CLUSTER default;
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE dataset_run_items_rmt ON CLUSTER default (
|
||||
-- primary identifiers
|
||||
`id` String,
|
||||
`project_id` String,
|
||||
`dataset_run_id` String,
|
||||
`dataset_item_id` String,
|
||||
`dataset_id` String,
|
||||
`trace_id` String,
|
||||
`observation_id` Nullable(String),
|
||||
|
||||
-- error field
|
||||
`error` Nullable(String),
|
||||
|
||||
-- timestamps
|
||||
`created_at` DateTime64(3) DEFAULT now(),
|
||||
`updated_at` DateTime64(3) DEFAULT now(),
|
||||
|
||||
-- denormalized immutable dataset run fields
|
||||
`dataset_run_name` String,
|
||||
`dataset_run_description` Nullable(String),
|
||||
`dataset_run_metadata` Map(LowCardinality(String), String),
|
||||
`dataset_run_created_at` DateTime64(3),
|
||||
|
||||
-- denormalized dataset item fields (mutable, but snapshots are relevant)
|
||||
`dataset_item_input` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_expected_output` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_metadata` Map(LowCardinality(String), String),
|
||||
|
||||
-- clickhouse engine fields
|
||||
`event_ts` DateTime64(3),
|
||||
`is_deleted` UInt8,
|
||||
|
||||
-- For dataset item lookups
|
||||
INDEX idx_dataset_item dataset_item_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
) ENGINE = ReplicatedReplacingMergeTree(event_ts, is_deleted)
|
||||
ORDER BY (project_id, dataset_id, dataset_run_id, id);
|
||||
@@ -1 +1 @@
|
||||
ALTER TABLE traces ON CLUSTER default DROP INDEX IF EXISTS idx_user_id;
|
||||
ALTER TABLE traces DROP INDEX IF EXISTS idx_user_id;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE dataset_run_items;
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE dataset_run_items (
|
||||
-- primary identifiers
|
||||
`id` String,
|
||||
`project_id` String,
|
||||
`dataset_run_id` String,
|
||||
`dataset_item_id` String,
|
||||
`dataset_id` String,
|
||||
`trace_id` String,
|
||||
`observation_id` Nullable(String),
|
||||
|
||||
-- error field
|
||||
`error` Nullable(String),
|
||||
|
||||
-- timestamps
|
||||
`created_at` DateTime64(3) DEFAULT now(),
|
||||
`updated_at` DateTime64(3) DEFAULT now(),
|
||||
|
||||
-- denormalized immutable dataset run fields
|
||||
`dataset_run_name` String,
|
||||
`dataset_run_description` Nullable(String),
|
||||
`dataset_run_metadata` Map(LowCardinality(String), String),
|
||||
`dataset_run_created_at` DateTime64(3),
|
||||
|
||||
-- denormalized dataset item fields (mutable, but snapshots are relevant)
|
||||
`dataset_item_input` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_expected_output` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_metadata` Map(LowCardinality(String), String),
|
||||
|
||||
-- clickhouse engine fields
|
||||
`event_ts` DateTime64(3),
|
||||
`is_deleted` UInt8,
|
||||
|
||||
-- For dataset item lookups
|
||||
INDEX idx_dataset_item dataset_item_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
) ENGINE = ReplacingMergeTree(event_ts, is_deleted)
|
||||
ORDER BY (project_id, dataset_id, dataset_run_id, id);
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
-- Drop materialized views first
|
||||
DROP VIEW IF EXISTS traces_30d_amt_mv;
|
||||
DROP VIEW IF EXISTS traces_7d_amt_mv;
|
||||
DROP VIEW IF EXISTS traces_all_amt_mv;
|
||||
|
||||
-- Drop AMT tables
|
||||
DROP TABLE IF EXISTS traces_30d_amt;
|
||||
DROP TABLE IF EXISTS traces_7d_amt;
|
||||
DROP TABLE IF EXISTS traces_all_amt;
|
||||
|
||||
-- Drop the Null table
|
||||
DROP TABLE IF EXISTS traces_null;
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
-- Create a Null table that serves as a trigger for all materialized views.
|
||||
-- We use a Null engine here to avoid storing intermediate results and save on storage.
|
||||
CREATE TABLE traces_null
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`start_time` DateTime64(3),
|
||||
`end_time` Nullable(DateTime64(3)),
|
||||
`name` Nullable(String),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` Map(LowCardinality(String), String),
|
||||
`user_id` Nullable(String),
|
||||
`session_id` Nullable(String),
|
||||
`environment` String,
|
||||
`tags` Array(String),
|
||||
`version` Nullable(String),
|
||||
`release` Nullable(String),
|
||||
|
||||
-- UI properties - We make them nullable to prevent absent values being interpreted as overwrites.
|
||||
`bookmarked` Nullable(Bool),
|
||||
`public` Nullable(Bool),
|
||||
|
||||
-- Aggregations
|
||||
`observation_ids` Array(String),
|
||||
`score_ids` Array(String),
|
||||
`cost_details` Map(String, Decimal64(12)),
|
||||
`usage_details` Map(String, UInt64),
|
||||
-- TODO: Do we want to aggregate/collect `levels` seen within the trace?
|
||||
|
||||
-- Input/Output
|
||||
`input` String,
|
||||
`output` String,
|
||||
|
||||
`created_at` DateTime64(3),
|
||||
`updated_at` DateTime64(3),
|
||||
`event_ts` DateTime64(3)
|
||||
) Engine = Null();
|
||||
|
||||
-- Create the all AMT
|
||||
CREATE TABLE traces_all_amt
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`timestamp` SimpleAggregateFunction(min, DateTime64(3)), -- Backward compatibility: redundant with start_time
|
||||
`start_time` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`end_time` SimpleAggregateFunction(max, Nullable(DateTime64(3))),
|
||||
`name` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` SimpleAggregateFunction(maxMap, Map(String, String)),
|
||||
`user_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`session_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`environment` SimpleAggregateFunction(anyLast, String),
|
||||
`tags` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`version` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`release` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- UI properties
|
||||
`bookmarked` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
`public` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
|
||||
-- Aggregations
|
||||
`observation_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`score_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`cost_details` SimpleAggregateFunction(sumMap, Map(String, Decimal(38, 12))),
|
||||
`usage_details` SimpleAggregateFunction(sumMap, Map(String, UInt64)),
|
||||
|
||||
-- Input/Output -> prefer correctness via argMax
|
||||
`input` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
`output` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
|
||||
`created_at` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`updated_at` SimpleAggregateFunction(max, DateTime64(3)),
|
||||
|
||||
-- Indexes
|
||||
INDEX idx_trace_id id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_user_id user_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_session_id session_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_name name TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_version version TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_release release TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_tags tags TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) Engine = AggregatingMergeTree()
|
||||
ORDER BY (project_id, id);
|
||||
|
||||
-- Create materialized view for all_amt
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS traces_all_amt_mv TO traces_all_amt AS
|
||||
SELECT
|
||||
-- Identifiers
|
||||
tn.project_id as project_id,
|
||||
tn.id as id,
|
||||
min(tn.start_time) as timestamp, -- Backward compatibility: redundant with start_time
|
||||
min(tn.start_time) as start_time,
|
||||
max(coalesce(tn.end_time, tn.start_time)) as end_time,
|
||||
anyLast(tn.name) as name,
|
||||
|
||||
-- Metadata properties
|
||||
maxMap(tn.metadata) as metadata,
|
||||
anyLast(tn.user_id) as user_id,
|
||||
anyLast(tn.session_id) as session_id,
|
||||
anyLast(tn.environment) as environment,
|
||||
groupUniqArrayArray(tn.tags) as tags,
|
||||
anyLast(tn.version) as version,
|
||||
anyLast(tn.release) as release,
|
||||
|
||||
-- UI properties
|
||||
argMaxState(tn.bookmarked, if(tn.bookmarked is not null, tn.event_ts, toDateTime64(0, 3))) as bookmarked,
|
||||
argMaxState(tn.public, if(tn.public is not null, tn.event_ts, toDateTime64(0, 3))) as public,
|
||||
|
||||
-- Aggregations
|
||||
groupUniqArrayArray(tn.observation_ids) as observation_ids,
|
||||
groupUniqArrayArray(tn.score_ids) as score_ids,
|
||||
sumMap(tn.cost_details) as cost_details,
|
||||
sumMap(tn.usage_details) as usage_details,
|
||||
|
||||
-- Input/Output
|
||||
argMaxState(tn.input, if(tn.input <> '', tn.event_ts, toDateTime64(0, 3))) as input,
|
||||
argMaxState(tn.output, if(tn.output <> '', tn.event_ts, toDateTime64(0, 3))) as output,
|
||||
|
||||
min(tn.created_at) as created_at,
|
||||
max(tn.updated_at) as updated_at
|
||||
FROM traces_null tn
|
||||
GROUP BY project_id, id;
|
||||
|
||||
-- Create the 7-day TTL AMT
|
||||
CREATE TABLE traces_7d_amt
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`timestamp` SimpleAggregateFunction(min, DateTime64(3)), -- Backward compatibility: redundant with start_time
|
||||
`start_time` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`end_time` SimpleAggregateFunction(max, Nullable(DateTime64(3))),
|
||||
`name` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` SimpleAggregateFunction(maxMap, Map(String, String)),
|
||||
`user_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`session_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`environment` SimpleAggregateFunction(anyLast, String),
|
||||
`tags` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`version` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`release` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- UI properties
|
||||
`bookmarked` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
`public` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
|
||||
-- Aggregations
|
||||
`observation_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`score_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`cost_details` SimpleAggregateFunction(sumMap, Map(String, Decimal(38, 12))),
|
||||
`usage_details` SimpleAggregateFunction(sumMap, Map(String, UInt64)),
|
||||
|
||||
-- Input/Output -> prefer correctness via argMax
|
||||
`input` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
`output` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
|
||||
`created_at` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`updated_at` SimpleAggregateFunction(max, DateTime64(3)),
|
||||
|
||||
-- Indexes
|
||||
INDEX idx_user_id user_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_session_id session_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_name name TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_version version TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_release release TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_tags tags TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) Engine = AggregatingMergeTree()
|
||||
ORDER BY (project_id, id)
|
||||
TTL toDate(start_time) + INTERVAL 7 DAY;
|
||||
|
||||
-- Create materialized view for 7d_amt
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS traces_7d_amt_mv TO traces_7d_amt AS
|
||||
SELECT
|
||||
-- Identifiers
|
||||
tn.project_id as project_id,
|
||||
tn.id as id,
|
||||
min(tn.start_time) as timestamp, -- Backward compatibility: redundant with start_time
|
||||
min(tn.start_time) as start_time,
|
||||
max(coalesce(tn.end_time, tn.start_time)) as end_time,
|
||||
anyLast(tn.name) as name,
|
||||
|
||||
-- Metadata properties
|
||||
maxMap(tn.metadata) as metadata,
|
||||
anyLast(tn.user_id) as user_id,
|
||||
anyLast(tn.session_id) as session_id,
|
||||
anyLast(tn.environment) as environment,
|
||||
groupUniqArrayArray(tn.tags) as tags,
|
||||
anyLast(tn.version) as version,
|
||||
anyLast(tn.release) as release,
|
||||
|
||||
-- UI properties
|
||||
argMaxState(tn.bookmarked, if(tn.bookmarked is not null, tn.event_ts, toDateTime64(0, 3))) as bookmarked,
|
||||
argMaxState(tn.public, if(tn.public is not null, tn.event_ts, toDateTime64(0, 3))) as public,
|
||||
|
||||
-- Aggregations
|
||||
groupUniqArrayArray(tn.observation_ids) as observation_ids,
|
||||
groupUniqArrayArray(tn.score_ids) as score_ids,
|
||||
sumMap(tn.cost_details) as cost_details,
|
||||
sumMap(tn.usage_details) as usage_details,
|
||||
|
||||
-- Input/Output
|
||||
argMaxState(tn.input, if(tn.input <> '', tn.event_ts, toDateTime64(0, 3))) as input,
|
||||
argMaxState(tn.output, if(tn.output <> '', tn.event_ts, toDateTime64(0, 3))) as output,
|
||||
|
||||
min(tn.created_at) as created_at,
|
||||
max(tn.updated_at) as updated_at
|
||||
FROM traces_null tn
|
||||
GROUP BY project_id, id;
|
||||
|
||||
-- Create the 30-day TTL AMT
|
||||
CREATE TABLE traces_30d_amt
|
||||
(
|
||||
-- Identifiers
|
||||
`project_id` String,
|
||||
`id` String,
|
||||
`timestamp` SimpleAggregateFunction(min, DateTime64(3)), -- Backward compatibility: redundant with start_time
|
||||
`start_time` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`end_time` SimpleAggregateFunction(max, Nullable(DateTime64(3))),
|
||||
`name` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- Metadata properties
|
||||
`metadata` SimpleAggregateFunction(maxMap, Map(String, String)),
|
||||
`user_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`session_id` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`environment` SimpleAggregateFunction(anyLast, String),
|
||||
`tags` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`version` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
`release` SimpleAggregateFunction(anyLast, Nullable(String)),
|
||||
|
||||
-- UI properties
|
||||
`bookmarked` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
`public` AggregateFunction(argMax, Nullable(Bool), DateTime64(3)),
|
||||
|
||||
-- Aggregations
|
||||
`observation_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`score_ids` SimpleAggregateFunction(groupUniqArrayArray, Array(String)),
|
||||
`cost_details` SimpleAggregateFunction(sumMap, Map(String, Decimal(38, 12))),
|
||||
`usage_details` SimpleAggregateFunction(sumMap, Map(String, UInt64)),
|
||||
|
||||
-- Input/Output -> prefer correctness via argMax
|
||||
`input` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
`output` AggregateFunction(argMax, String, DateTime64(3)) CODEC (ZSTD(3)),
|
||||
|
||||
`created_at` SimpleAggregateFunction(min, DateTime64(3)),
|
||||
`updated_at` SimpleAggregateFunction(max, DateTime64(3)),
|
||||
|
||||
-- Indexes
|
||||
INDEX idx_user_id user_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_session_id session_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_name name TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_version version TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_release release TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
INDEX idx_tags tags TYPE bloom_filter(0.001) GRANULARITY 1
|
||||
) Engine = AggregatingMergeTree()
|
||||
ORDER BY (project_id, id)
|
||||
TTL toDate(start_time) + INTERVAL 30 DAY;
|
||||
|
||||
-- Create materialized view for 30d_amt
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS traces_30d_amt_mv TO traces_30d_amt AS
|
||||
SELECT
|
||||
-- Identifiers
|
||||
tn.project_id as project_id,
|
||||
tn.id as id,
|
||||
min(tn.start_time) as timestamp, -- Backward compatibility: redundant with start_time
|
||||
min(tn.start_time) as start_time,
|
||||
max(coalesce(tn.end_time, tn.start_time)) as end_time,
|
||||
anyLast(tn.name) as name,
|
||||
|
||||
-- Metadata properties
|
||||
maxMap(tn.metadata) as metadata,
|
||||
anyLast(tn.user_id) as user_id,
|
||||
anyLast(tn.session_id) as session_id,
|
||||
anyLast(tn.environment) as environment,
|
||||
groupUniqArrayArray(tn.tags) as tags,
|
||||
anyLast(tn.version) as version,
|
||||
anyLast(tn.release) as release,
|
||||
|
||||
-- UI properties
|
||||
argMaxState(tn.bookmarked, if(tn.bookmarked is not null, tn.event_ts, toDateTime64(0, 3))) as bookmarked,
|
||||
argMaxState(tn.public, if(tn.public is not null, tn.event_ts, toDateTime64(0, 3))) as public,
|
||||
|
||||
-- Aggregations
|
||||
groupUniqArrayArray(tn.observation_ids) as observation_ids,
|
||||
groupUniqArrayArray(tn.score_ids) as score_ids,
|
||||
sumMap(tn.cost_details) as cost_details,
|
||||
sumMap(tn.usage_details) as usage_details,
|
||||
|
||||
-- Input/Output
|
||||
argMaxState(tn.input, if(tn.input <> '', tn.event_ts, toDateTime64(0, 3))) as input,
|
||||
argMaxState(tn.output, if(tn.output <> '', tn.event_ts, toDateTime64(0, 3))) as output,
|
||||
|
||||
min(tn.created_at) as created_at,
|
||||
max(tn.updated_at) as updated_at
|
||||
FROM traces_null tn
|
||||
GROUP BY project_id, id;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE dataset_run_items_rmt;
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE dataset_run_items_rmt (
|
||||
-- primary identifiers
|
||||
`id` String,
|
||||
`project_id` String,
|
||||
`dataset_run_id` String,
|
||||
`dataset_item_id` String,
|
||||
`dataset_id` String,
|
||||
`trace_id` String,
|
||||
`observation_id` Nullable(String),
|
||||
|
||||
-- error field
|
||||
`error` Nullable(String),
|
||||
|
||||
-- timestamps
|
||||
`created_at` DateTime64(3) DEFAULT now(),
|
||||
`updated_at` DateTime64(3) DEFAULT now(),
|
||||
|
||||
-- denormalized immutable dataset run fields
|
||||
`dataset_run_name` String,
|
||||
`dataset_run_description` Nullable(String),
|
||||
`dataset_run_metadata` Map(LowCardinality(String), String),
|
||||
`dataset_run_created_at` DateTime64(3),
|
||||
|
||||
-- denormalized dataset item fields (mutable, but snapshots are relevant)
|
||||
`dataset_item_input` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_expected_output` Nullable(String) CODEC(ZSTD(3)), -- json
|
||||
`dataset_item_metadata` Map(LowCardinality(String), String),
|
||||
|
||||
-- clickhouse engine fields
|
||||
`event_ts` DateTime64(3),
|
||||
`is_deleted` UInt8,
|
||||
|
||||
-- For dataset item lookups
|
||||
INDEX idx_dataset_item dataset_item_id TYPE bloom_filter(0.001) GRANULARITY 1,
|
||||
) ENGINE = ReplacingMergeTree(event_ts, is_deleted)
|
||||
ORDER BY (project_id, dataset_id, dataset_run_id, id);
|
||||
@@ -5,8 +5,30 @@
|
||||
|
||||
# Check if CLICKHOUSE_URL is configured
|
||||
if [ -z "${CLICKHOUSE_URL}" ]; then
|
||||
echo "Info: CLICKHOUSE_URL not configured, skipping migration."
|
||||
exit 0
|
||||
echo "Error: CLICKHOUSE_URL is not configured."
|
||||
echo "Please set CLICKHOUSE_URL in your environment variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if CLICKHOUSE_MIGRATION_URL is configured
|
||||
if [ -z "${CLICKHOUSE_MIGRATION_URL}" ]; then
|
||||
echo "Error: CLICKHOUSE_MIGRATION_URL is not configured."
|
||||
echo "Please set CLICKHOUSE_MIGRATION_URL in your environment variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if CLICKHOUSE_USER is set
|
||||
if [ -z "${CLICKHOUSE_USER}" ]; then
|
||||
echo "Error: CLICKHOUSE_USER is not set."
|
||||
echo "Please set CLICKHOUSE_USER in your environment variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if CLICKHOUSE_PASSWORD is set
|
||||
if [ -z "${CLICKHOUSE_PASSWORD}" ]; then
|
||||
echo "Error: CLICKHOUSE_PASSWORD is not set."
|
||||
echo "Please set CLICKHOUSE_PASSWORD in your environment variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if golang-migrate is installed
|
||||
|
||||
@@ -33,11 +33,12 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 72",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0",
|
||||
"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",
|
||||
"db:reset": "dotenv -e ../../.env npx -- prisma migrate reset",
|
||||
"db:reset:test": "if [ -f ../../.env.test ]; then dotenv -e ../../.env.test -e ../../.env -- npx prisma migrate reset --force; fi",
|
||||
"db:deploy": "dotenv -e ../../.env npx -- prisma migrate deploy",
|
||||
"db:seed": "dotenv -e ../../.env -- npx prisma db seed",
|
||||
"db:generate": "dotenv -e ../../.env -- npx prisma generate",
|
||||
@@ -60,7 +61,7 @@
|
||||
"@aws-sdk/lib-storage": "^3.675.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.679.0",
|
||||
"@azure/storage-blob": "^12.26.0",
|
||||
"@clickhouse/client": "^1.11.2",
|
||||
"@clickhouse/client": "^1.12.0",
|
||||
"@google-cloud/storage": "^7.15.2",
|
||||
"@langchain/anthropic": "^0.3.22",
|
||||
"@langchain/aws": "^0.1.11",
|
||||
@@ -72,19 +73,20 @@
|
||||
"@prisma/client": "^6.10.1",
|
||||
"@react-email/components": "^0.1.0",
|
||||
"@react-email/render": "^1.1.2",
|
||||
"@slack/oauth": "^3.0.3",
|
||||
"@slack/web-api": "^7.9.3",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"axios": "^1.8.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bullmq": "^5.34.10",
|
||||
"dd-trace": "^5.36.0",
|
||||
"decimal.js": "^10.4.3",
|
||||
"exponential-backoff": "^3.1.1",
|
||||
"exponential-backoff": "^3.1.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"ioredis": "^5.4.1",
|
||||
"jsonpath-plus": "10.3.0",
|
||||
"kysely": "^0.27.4",
|
||||
"langchain": "^0.3.28",
|
||||
"langfuse-langchain": "3.37.4",
|
||||
"langfuse-langchain": "3.38.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lossless-json": "^4.1.1",
|
||||
"next-auth": "^4.24.11",
|
||||
@@ -111,23 +113,16 @@
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"kysely-codegen": "^0.16.8",
|
||||
"nodemon": "^3.1.7",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.6.2",
|
||||
"prisma": "^6.10.1",
|
||||
"prisma-erd-generator": "^1.11.2",
|
||||
"prisma-kysely": "^1.8.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.2.0",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^2.1.2"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "~18.2.79",
|
||||
"react": "~18.2.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"nanoid": "^3.3.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ActionType" AS ENUM ('WEBHOOK');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ActionExecutionStatus" AS ENUM ('COMPLETED', 'ERROR', 'PENDING', 'CANCELLED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "actions" (
|
||||
"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,
|
||||
"type" "ActionType" NOT NULL,
|
||||
"config" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "actions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "triggers" (
|
||||
"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,
|
||||
"eventSource" TEXT NOT NULL,
|
||||
"eventActions" TEXT[],
|
||||
"filter" JSONB,
|
||||
"status" "JobConfigState" NOT NULL DEFAULT 'ACTIVE',
|
||||
|
||||
CONSTRAINT "triggers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "automations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"trigger_id" TEXT NOT NULL,
|
||||
"action_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"project_id" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "automations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "automation_executions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"source_id" TEXT NOT NULL,
|
||||
"automation_id" TEXT NOT NULL,
|
||||
"trigger_id" TEXT NOT NULL,
|
||||
"action_id" TEXT NOT NULL,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"status" "ActionExecutionStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"input" JSONB NOT NULL,
|
||||
"output" JSONB,
|
||||
"started_at" TIMESTAMP(3),
|
||||
"finished_at" TIMESTAMP(3),
|
||||
"error" TEXT,
|
||||
|
||||
CONSTRAINT "automation_executions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "actions_project_id_idx" ON "actions"("project_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "triggers_project_id_idx" ON "triggers"("project_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automations_project_id_action_id_trigger_id_idx" ON "automations"("project_id", "action_id", "trigger_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automations_project_id_name_idx" ON "automations"("project_id", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automation_executions_trigger_id_idx" ON "automation_executions"("trigger_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automation_executions_action_id_idx" ON "automation_executions"("action_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "automation_executions_project_id_idx" ON "automation_executions"("project_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "actions" ADD CONSTRAINT "actions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "triggers" ADD CONSTRAINT "triggers_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automations" ADD CONSTRAINT "automations_trigger_id_fkey" FOREIGN KEY ("trigger_id") REFERENCES "triggers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automations" ADD CONSTRAINT "automations_action_id_fkey" FOREIGN KEY ("action_id") REFERENCES "actions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automations" ADD CONSTRAINT "automations_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automation_executions" ADD CONSTRAINT "automation_executions_automation_id_fkey" FOREIGN KEY ("automation_id") REFERENCES "automations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automation_executions" ADD CONSTRAINT "automation_executions_trigger_id_fkey" FOREIGN KEY ("trigger_id") REFERENCES "triggers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automation_executions" ADD CONSTRAINT "automation_executions_action_id_fkey" FOREIGN KEY ("action_id") REFERENCES "actions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "automation_executions" ADD CONSTRAINT "automation_executions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BlobStorageExportMode" AS ENUM ('FULL_HISTORY', 'FROM_TODAY', 'FROM_CUSTOM_DATE');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "blob_storage_integrations" ADD COLUMN "export_mode" "BlobStorageExportMode" NOT NULL DEFAULT 'FULL_HISTORY',
|
||||
ADD COLUMN "export_start_date" TIMESTAMP(3);
|
||||
@@ -0,0 +1,13 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "prices"
|
||||
ADD COLUMN "project_id" TEXT;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "prices"
|
||||
ADD CONSTRAINT "prices_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects" ("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- BackfillData
|
||||
UPDATE "prices"
|
||||
SET "project_id" = (SELECT "models"."project_id"
|
||||
FROM "models"
|
||||
WHERE "models"."id" = "prices"."model_id");
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('3445cac4-d9d5-4750-8b65-351135c1b85e', '20250711_1347_patch_llm_tool_schema_audit_logs', 'patchLLMToolAndLLLMSchemaAuditLogs', '{}');
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS "trace_sessions_project_id_created_at_idx" ON "trace_sessions"("project_id", "created_at" DESC);
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- DropIndex
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "trace_sessions_created_at_idx";
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- DropIndex
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "trace_sessions_project_id_idx";
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- DropIndex
|
||||
DROP INDEX CONCURRENTLY IF EXISTS "trace_sessions_updated_at_idx";
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "datasets" ADD COLUMN "remote_experiment_payload" JSONB,
|
||||
ADD COLUMN "remote_experiment_url" TEXT;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "AnnotationQueueObjectType" ADD VALUE 'SESSION';
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Migration: Add Slack Integration Support
|
||||
-- This migration adds support for Slack automation actions by:
|
||||
-- 1. Adding SLACK to the ActionType enum
|
||||
-- 2. Creating slack_integrations table for centralized token storage
|
||||
|
||||
-- AlterEnum
|
||||
ALTER TYPE "ActionType" ADD VALUE 'SLACK';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "slack_integrations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"team_id" TEXT NOT NULL,
|
||||
"team_name" TEXT NOT NULL,
|
||||
"bot_token" TEXT NOT NULL,
|
||||
"bot_user_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "slack_integrations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "slack_integrations_project_id_key" ON "slack_integrations"("project_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "slack_integrations_team_id_idx" ON "slack_integrations"("team_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "slack_integrations" ADD CONSTRAINT "slack_integrations_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('8d47f91b-3e5c-4a26-9f85-c12d6e4b9a3d', '20250731_1001_migrate_dataset_run_items_pg_to_ch', 'migrateDatasetRunItemsFromPostgresToClickhouse', '{}');
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "pending_deletions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"object" TEXT NOT NULL,
|
||||
"object_id" TEXT NOT NULL,
|
||||
"is_deleted" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "pending_deletions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pending_deletions_project_id_object_is_deleted_idx" ON "pending_deletions"("project_id", "object", "is_deleted");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pending_deletions_object_id_object_idx" ON "pending_deletions"("object_id", "object");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "pending_deletions" ADD CONSTRAINT "pending_deletions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "annotation_queue_assignments" (
|
||||
"id" TEXT NOT NULL,
|
||||
"project_id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"queue_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "annotation_queue_assignments_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "annotation_queue_assignments_project_id_queue_id_key" ON "annotation_queue_assignments"("project_id", "queue_id", "user_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queue_assignments" ADD CONSTRAINT "annotation_queue_assignments_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queue_assignments" ADD CONSTRAINT "annotation_queue_assignments_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotation_queue_assignments" ADD CONSTRAINT "annotation_queue_assignments_queue_id_fkey" FOREIGN KEY ("queue_id") REFERENCES "annotation_queues"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SurveyName" AS ENUM ('org_onboarding', 'user_onboarding');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "surveys" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"survey_name" "SurveyName" NOT NULL,
|
||||
"response" JSONB NOT NULL,
|
||||
"user_id" TEXT,
|
||||
"user_email" TEXT,
|
||||
"org_id" TEXT,
|
||||
|
||||
CONSTRAINT "surveys_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "surveys" ADD CONSTRAINT "surveys_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "surveys" ADD CONSTRAINT "surveys_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- RenameIndex
|
||||
ALTER INDEX "annotation_queue_assignments_project_id_queue_id_key" RENAME TO "annotation_queue_assignments_project_id_queue_id_user_id_key";
|
||||
+1
@@ -0,0 +1 @@
|
||||
DELETE FROM background_migrations WHERE id = '8d47f91b-3e5c-4a26-9f85-c12d6e4b9a3d';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('9f32e84c-7b1d-4f59-a803-d67ae5c9b2e8', '20250814_1001_migrate_dataset_run_items_rmt_pg_to_ch', 'migrateDatasetRunItemsFromPostgresToClickhouseRmt', '{}');
|
||||
@@ -1,3 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
provider = "postgresql"
|
||||
|
||||
@@ -65,29 +65,31 @@ model Session {
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime? @map("email_verified")
|
||||
password String?
|
||||
image String?
|
||||
admin Boolean @default(false)
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
organizationMemberships OrganizationMembership[]
|
||||
projectMemberships ProjectMembership[]
|
||||
invitations MembershipInvitation[]
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
featureFlags String[] @default([]) @map("feature_flags")
|
||||
annotatedLockedItem AnnotationQueueItem[] @relation("LockedByUser")
|
||||
annotatedCompletedItem AnnotationQueueItem[] @relation("AnnotatorUser")
|
||||
dashboardWidgetsCreated DashboardWidget[] @relation("CreatedByUser")
|
||||
dashboardWidgetsUpdated DashboardWidget[] @relation("UpdatedByUser")
|
||||
dashboardCreated Dashboard[] @relation("CreatedByUser")
|
||||
dashboardUpdated Dashboard[] @relation("UpdatedByUser")
|
||||
tableViewPresetCreated TableViewPreset[] @relation("CreatedByUser")
|
||||
tableViewPresetUpdated TableViewPreset[] @relation("UpdatedByUser")
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime? @map("email_verified")
|
||||
password String?
|
||||
image String?
|
||||
admin Boolean @default(false)
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
organizationMemberships OrganizationMembership[]
|
||||
projectMemberships ProjectMembership[]
|
||||
invitations MembershipInvitation[]
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
featureFlags String[] @default([]) @map("feature_flags")
|
||||
annotatedLockedItem AnnotationQueueItem[] @relation("LockedByUser")
|
||||
annotatedCompletedItem AnnotationQueueItem[] @relation("AnnotatorUser")
|
||||
dashboardWidgetsCreated DashboardWidget[] @relation("CreatedByUser")
|
||||
dashboardWidgetsUpdated DashboardWidget[] @relation("UpdatedByUser")
|
||||
dashboardCreated Dashboard[] @relation("CreatedByUser")
|
||||
dashboardUpdated Dashboard[] @relation("UpdatedByUser")
|
||||
tableViewPresetCreated TableViewPreset[] @relation("CreatedByUser")
|
||||
tableViewPresetUpdated TableViewPreset[] @relation("UpdatedByUser")
|
||||
annotationQueueAssignment AnnotationQueueAssignment[]
|
||||
surveys Survey[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -112,52 +114,61 @@ model Organization {
|
||||
projects Project[]
|
||||
MembershipInvitation MembershipInvitation[]
|
||||
ApiKey ApiKey[]
|
||||
surveys Survey[]
|
||||
|
||||
@@map("organizations")
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(cuid())
|
||||
orgId String @map("org_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
name String
|
||||
retentionDays Int? @map("retention_days")
|
||||
metadata Json?
|
||||
projectMembers ProjectMembership[]
|
||||
organization Organization @relation(fields: [orgId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
apiKeys ApiKey[]
|
||||
dataset Dataset[]
|
||||
invitations MembershipInvitation[]
|
||||
sessions TraceSession[]
|
||||
Prompt Prompt[]
|
||||
Model Model[]
|
||||
EvalTemplate EvalTemplate[]
|
||||
JobConfiguration JobConfiguration[]
|
||||
JobExecution JobExecution[]
|
||||
LlmApiKeys LlmApiKeys[]
|
||||
PosthogIntegration PosthogIntegration[]
|
||||
BlobStorageIntegration BlobStorageIntegration[]
|
||||
scoreConfig ScoreConfig[]
|
||||
BatchExport BatchExport[]
|
||||
comment Comment[]
|
||||
annotationQueue AnnotationQueue[]
|
||||
annotationQueueItem AnnotationQueueItem[]
|
||||
TraceMedia TraceMedia[]
|
||||
Media Media[]
|
||||
ObservationMedia ObservationMedia[]
|
||||
LegacyTrace LegacyPrismaTrace[]
|
||||
LegacyObservation LegacyPrismaObservation[]
|
||||
LegacyScore LegacyPrismaScore[]
|
||||
PromptDependency PromptDependency[]
|
||||
LlmSchema LlmSchema[]
|
||||
LlmTool LlmTool[]
|
||||
PromptProtectedLabels PromptProtectedLabels[]
|
||||
Dashboard Dashboard[]
|
||||
DashboardWidget DashboardWidget[]
|
||||
TableViewPreset TableViewPreset[]
|
||||
DefaultLlmModel DefaultLlmModel[]
|
||||
id String @id @default(cuid())
|
||||
orgId String @map("org_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
name String
|
||||
retentionDays Int? @map("retention_days")
|
||||
metadata Json?
|
||||
projectMembers ProjectMembership[]
|
||||
organization Organization @relation(fields: [orgId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
apiKeys ApiKey[]
|
||||
dataset Dataset[]
|
||||
invitations MembershipInvitation[]
|
||||
sessions TraceSession[]
|
||||
Prompt Prompt[]
|
||||
Model Model[]
|
||||
EvalTemplate EvalTemplate[]
|
||||
JobConfiguration JobConfiguration[]
|
||||
JobExecution JobExecution[]
|
||||
LlmApiKeys LlmApiKeys[]
|
||||
PosthogIntegration PosthogIntegration[]
|
||||
BlobStorageIntegration BlobStorageIntegration[]
|
||||
scoreConfig ScoreConfig[]
|
||||
BatchExport BatchExport[]
|
||||
comment Comment[]
|
||||
annotationQueue AnnotationQueue[]
|
||||
annotationQueueItem AnnotationQueueItem[]
|
||||
TraceMedia TraceMedia[]
|
||||
Media Media[]
|
||||
ObservationMedia ObservationMedia[]
|
||||
LegacyTrace LegacyPrismaTrace[]
|
||||
LegacyObservation LegacyPrismaObservation[]
|
||||
LegacyScore LegacyPrismaScore[]
|
||||
PromptDependency PromptDependency[]
|
||||
LlmSchema LlmSchema[]
|
||||
LlmTool LlmTool[]
|
||||
PromptProtectedLabels PromptProtectedLabels[]
|
||||
Dashboard Dashboard[]
|
||||
DashboardWidget DashboardWidget[]
|
||||
TableViewPreset TableViewPreset[]
|
||||
actions Action[]
|
||||
triggers Trigger[]
|
||||
automationExecutions AutomationExecution[]
|
||||
Automation Automation[]
|
||||
DefaultLlmModel DefaultLlmModel[]
|
||||
Price Price[]
|
||||
SlackIntegration SlackIntegration?
|
||||
PendingDeletion PendingDeletion[]
|
||||
AnnotationQueueAssignment AnnotationQueueAssignment[]
|
||||
|
||||
@@index([orgId])
|
||||
@@map("projects")
|
||||
@@ -306,9 +317,7 @@ model TraceSession {
|
||||
environment String @default("default")
|
||||
|
||||
@@id([id, projectId])
|
||||
@@index([projectId])
|
||||
@@index([createdAt])
|
||||
@@index([updatedAt])
|
||||
@@index([projectId, createdAt(sort: Desc)])
|
||||
@@map("trace_sessions")
|
||||
}
|
||||
|
||||
@@ -359,7 +368,6 @@ model LegacyPrismaObservation {
|
||||
version String?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
// GENERATION ONLY
|
||||
model String? // user-provided model attribute
|
||||
internalModel String? @map("internal_model") // matched model.name that is matched at ingestion time, to be deprecated
|
||||
internalModelId String? @map("internal_model_id") // matched model.id that is matched at ingestion time
|
||||
@@ -400,6 +408,13 @@ enum LegacyPrismaObservationType {
|
||||
SPAN
|
||||
EVENT
|
||||
GENERATION
|
||||
AGENT
|
||||
TOOL
|
||||
CHAIN
|
||||
RETRIEVER
|
||||
EVALUATOR
|
||||
EMBEDDING
|
||||
GUARDRAIL
|
||||
|
||||
@@map("ObservationType")
|
||||
}
|
||||
@@ -486,15 +501,16 @@ enum ScoreDataType {
|
||||
}
|
||||
|
||||
model AnnotationQueue {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
description String?
|
||||
scoreConfigIds String[] @default([]) @map("score_config_ids")
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
annotationQueueItem AnnotationQueueItem[]
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
description String?
|
||||
scoreConfigIds String[] @default([]) @map("score_config_ids")
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
annotationQueueItem AnnotationQueueItem[]
|
||||
annotationQueueAssignment AnnotationQueueAssignment[]
|
||||
|
||||
@@unique([projectId, name])
|
||||
@@index([id, projectId])
|
||||
@@ -536,6 +552,22 @@ enum AnnotationQueueStatus {
|
||||
enum AnnotationQueueObjectType {
|
||||
TRACE
|
||||
OBSERVATION
|
||||
SESSION
|
||||
}
|
||||
|
||||
model AnnotationQueueAssignment {
|
||||
id String @id @default(cuid())
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
userId String @map("user_id")
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
queueId String @map("queue_id")
|
||||
queue AnnotationQueue @relation(fields: [queueId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@unique([projectId, queueId, userId])
|
||||
@@map("annotation_queue_assignments")
|
||||
}
|
||||
|
||||
model CronJobs {
|
||||
@@ -548,16 +580,18 @@ model CronJobs {
|
||||
}
|
||||
|
||||
model Dataset {
|
||||
id String @default(cuid())
|
||||
projectId String @map("project_id")
|
||||
name String
|
||||
description String?
|
||||
metadata Json?
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
datasetItems DatasetItem[]
|
||||
datasetRuns DatasetRuns[]
|
||||
id String @default(cuid())
|
||||
projectId String @map("project_id")
|
||||
name String
|
||||
description String?
|
||||
metadata Json?
|
||||
remoteExperimentUrl String? @map("remote_experiment_url")
|
||||
remoteExperimentPayload Json? @map("remote_experiment_payload")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
datasetItems DatasetItem[]
|
||||
datasetRuns DatasetRuns[]
|
||||
|
||||
@@id([id, projectId])
|
||||
@@unique([projectId, name])
|
||||
@@ -751,6 +785,8 @@ model Price {
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
modelId String @map("model_id") // Model is already linked to project (or default), so we don't need projectId here
|
||||
Model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)
|
||||
projectId String? @map("project_id")
|
||||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
usageType String @map("usage_type")
|
||||
price Decimal
|
||||
|
||||
@@ -963,6 +999,8 @@ model BlobStorageIntegration {
|
||||
enabled Boolean
|
||||
exportFrequency String @map("export_frequency")
|
||||
fileType BlobStorageIntegrationFileType @default(CSV) @map("file_type")
|
||||
exportMode BlobStorageExportMode @default(FULL_HISTORY) @map("export_mode")
|
||||
exportStartDate DateTime? @map("export_start_date")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
@@ -986,6 +1024,14 @@ enum BlobStorageIntegrationType {
|
||||
@@map("BlobStorageIntegrationType")
|
||||
}
|
||||
|
||||
enum BlobStorageExportMode {
|
||||
FULL_HISTORY
|
||||
FROM_TODAY
|
||||
FROM_CUSTOM_DATE
|
||||
|
||||
@@map("BlobStorageExportMode")
|
||||
}
|
||||
|
||||
model BatchExport {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
@@ -1214,3 +1260,169 @@ model TableViewPreset {
|
||||
@@unique([projectId, tableName, name])
|
||||
@@map("table_view_presets")
|
||||
}
|
||||
|
||||
model Action {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
type ActionType
|
||||
|
||||
// Configuration specific to each action type
|
||||
config Json // Structured JSON for different action types
|
||||
// For WEBHOOK: { version: "1.0", url: "...", method: "POST", headers: {...}, secretId: "..." }
|
||||
// For ANNOTATION_QUEUE: { version: "1.0", queueId: "..." }
|
||||
|
||||
automations Automation[]
|
||||
automationExecutions AutomationExecution[]
|
||||
|
||||
@@index([projectId])
|
||||
@@map("actions")
|
||||
}
|
||||
|
||||
model Trigger {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
// When should this trigger fire
|
||||
eventSource String // trace, prompt, etc.
|
||||
eventActions String[] // created, updated, deleted
|
||||
filter Json? // Filter conditions (format: { field: "name", operator: "equals", value: "my_trace" })
|
||||
|
||||
// Additional attributes
|
||||
status JobConfigState @default(ACTIVE) @map("status")
|
||||
|
||||
// Link to executions
|
||||
automationExecutions AutomationExecution[]
|
||||
automations Automation[]
|
||||
|
||||
@@index([projectId])
|
||||
@@map("triggers")
|
||||
}
|
||||
|
||||
model Automation {
|
||||
id String @id @default(cuid())
|
||||
name String @map("name")
|
||||
trigger Trigger @relation(fields: [triggerId], references: [id], onDelete: Cascade)
|
||||
triggerId String @map("trigger_id")
|
||||
action Action @relation(fields: [actionId], references: [id], onDelete: Cascade)
|
||||
actionId String @map("action_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
AutomationExecution AutomationExecution[]
|
||||
|
||||
@@index([projectId, actionId, triggerId])
|
||||
@@index([projectId, name])
|
||||
@@map("automations")
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
WEBHOOK
|
||||
SLACK
|
||||
// More action types can be added as needed
|
||||
}
|
||||
|
||||
enum ActionExecutionStatus {
|
||||
COMPLETED
|
||||
ERROR
|
||||
PENDING
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model AutomationExecution {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
sourceId String @map("source_id")
|
||||
|
||||
automationId String @map("automation_id")
|
||||
automation Automation @relation(fields: [automationId], references: [id], onDelete: Cascade)
|
||||
|
||||
triggerId String @map("trigger_id")
|
||||
trigger Trigger @relation(fields: [triggerId], references: [id], onDelete: Cascade)
|
||||
|
||||
actionId String @map("action_id")
|
||||
action Action @relation(fields: [actionId], references: [id], onDelete: Cascade)
|
||||
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
status ActionExecutionStatus @default(PENDING) @map("status")
|
||||
input Json @map("input")
|
||||
output Json? @map("output")
|
||||
startedAt DateTime? @map("started_at")
|
||||
finishedAt DateTime? @map("finished_at")
|
||||
error String? @map("error")
|
||||
|
||||
@@index([triggerId])
|
||||
@@index([actionId])
|
||||
@@index([projectId])
|
||||
@@map("automation_executions")
|
||||
}
|
||||
|
||||
// Slack Integration: Stores centralized Slack workspace connection for each project
|
||||
// One project can connect to one Slack workspace, supporting multiple channel automations
|
||||
model SlackIntegration {
|
||||
id String @id @default(cuid())
|
||||
projectId String @unique @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
// Installation details (encrypted using shared encryption utilities)
|
||||
teamId String @map("team_id") // Slack workspace ID
|
||||
teamName String @map("team_name") // Human-readable workspace name
|
||||
botToken String @map("bot_token") // Encrypted bot token for API calls
|
||||
botUserId String @map("bot_user_id") // Bot user ID for workspace
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@index([teamId])
|
||||
@@map("slack_integrations")
|
||||
}
|
||||
|
||||
// Pending Deletions: Tracks objects (like traces) that are scheduled for batch deletion
|
||||
model PendingDeletion {
|
||||
id String @id @default(cuid())
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
object String @map("object") // e.g., "trace", "observation", etc.
|
||||
objectId String @map("object_id") // The ID of the object to be deleted
|
||||
isDeleted Boolean @default(false) @map("is_deleted")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@index([projectId, object, isDeleted])
|
||||
@@index([objectId, object])
|
||||
@@map("pending_deletions")
|
||||
}
|
||||
|
||||
model Survey {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
surveyName SurveyName @map("survey_name")
|
||||
response Json
|
||||
userId String? @map("user_id")
|
||||
userEmail String? @map("user_email")
|
||||
orgId String? @map("org_id")
|
||||
org Organization? @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("surveys")
|
||||
}
|
||||
|
||||
enum SurveyName {
|
||||
ORG_ONBOARDING @map("org_onboarding")
|
||||
USER_ONBOARDING @map("user_onboarding")
|
||||
|
||||
@@map("SurveyName")
|
||||
}
|
||||
|
||||
@@ -4,15 +4,16 @@ import { hash } from "bcryptjs";
|
||||
import { v4 } from "uuid";
|
||||
import { encrypt } from "../../src/encryption";
|
||||
import {
|
||||
type JobConfiguration,
|
||||
JobExecutionStatus,
|
||||
PrismaClient,
|
||||
type Project,
|
||||
ScoreDataType,
|
||||
type JobConfiguration,
|
||||
JobExecutionStatus,
|
||||
PrismaClient,
|
||||
type Project,
|
||||
ScoreDataType,
|
||||
} from "../../src/index";
|
||||
import { getDisplaySecretKey, hashSecretKey, logger } from "../../src/server";
|
||||
import { redis } from "../../src/server/redis/redis";
|
||||
import {EVAL_TRACE_COUNT,
|
||||
import {
|
||||
EVAL_TRACE_COUNT,
|
||||
FAILED_EVAL_TRACE_INTERVAL,
|
||||
SEED_CHAT_ML_PROMPTS,
|
||||
SEED_DATASETS,
|
||||
@@ -22,6 +23,7 @@ import {EVAL_TRACE_COUNT,
|
||||
SEED_TEXT_PROMPTS,
|
||||
} from "./utils/postgres-seed-constants";
|
||||
import {
|
||||
generateDatasetItemId,
|
||||
generateDatasetRunTraceId,
|
||||
generateEvalObservationId,
|
||||
generateEvalScoreId,
|
||||
@@ -178,7 +180,7 @@ async function main() {
|
||||
|
||||
const seedApiKey = {
|
||||
id: "seed-api-key",
|
||||
secret: process.env.SEED_SECRET_KEY ?? "sk-lf-1234567890",
|
||||
secret: process.env.SEED_SECRET_KEY ?? "sk-lf-1234567890", // eslint-disable-line turbo/no-undeclared-env-vars
|
||||
public: "pk-lf-1234567890",
|
||||
note: "seeded key",
|
||||
};
|
||||
@@ -241,7 +243,7 @@ async function main() {
|
||||
|
||||
const secondKey = {
|
||||
id: "seed-api-key-2",
|
||||
secret: process.env.SEED_SECRET_KEY ?? "sk-lf-asdfghjkl",
|
||||
secret: process.env.SEED_SECRET_KEY ?? "sk-lf-asdfghjkl", // eslint-disable-line turbo/no-undeclared-env-vars
|
||||
public: "pk-lf-asdfghjkl",
|
||||
note: "seeded key 2",
|
||||
};
|
||||
@@ -274,7 +276,7 @@ async function main() {
|
||||
await createTraceSessions(project1, project2);
|
||||
|
||||
// If openai key is in environment, add it to the projects LLM API keys
|
||||
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
const OPENAI_API_KEY = process.env.OPENAI_API_KEY; // eslint-disable-line turbo/no-undeclared-env-vars
|
||||
|
||||
if (OPENAI_API_KEY) {
|
||||
await prisma.llmApiKeys.create({
|
||||
@@ -516,6 +518,7 @@ export async function createDatasets(
|
||||
description: data.description,
|
||||
projectId,
|
||||
metadata: data.metadata,
|
||||
id: `${datasetName}-${projectId.slice(-8)}`,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -531,13 +534,23 @@ export async function createDatasets(
|
||||
const datasetItem = await prisma.datasetItem.upsert({
|
||||
where: {
|
||||
id_projectId: {
|
||||
id: `${dataset.id}-${index}`,
|
||||
id: generateDatasetItemId(
|
||||
datasetName,
|
||||
index,
|
||||
projectId,
|
||||
SEED_DATASETS.indexOf(data) || 0,
|
||||
),
|
||||
projectId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
projectId,
|
||||
id: `${dataset.id}-${index}`,
|
||||
id: generateDatasetItemId(
|
||||
datasetName,
|
||||
index,
|
||||
projectId,
|
||||
SEED_DATASETS.indexOf(data) || 0,
|
||||
),
|
||||
datasetId: dataset.id,
|
||||
sourceTraceId: sourceTraceId ?? null,
|
||||
sourceObservationId: null,
|
||||
@@ -553,14 +566,14 @@ export async function createDatasets(
|
||||
for (let datasetRunNumber = 0; datasetRunNumber < 3; datasetRunNumber++) {
|
||||
const datasetRun = await prisma.datasetRuns.upsert({
|
||||
where: {
|
||||
datasetId_projectId_name: {
|
||||
datasetId: dataset.id,
|
||||
id_projectId: {
|
||||
id: `demo-dataset-run-${datasetRunNumber}-${projectId.slice(-8)}`,
|
||||
projectId,
|
||||
name: `demo-dataset-run-${datasetRunNumber}`,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
projectId,
|
||||
id: `demo-dataset-run-${datasetRunNumber}-${projectId.slice(-8)}`,
|
||||
name: `demo-dataset-run-${datasetRunNumber}`,
|
||||
description: Math.random() > 0.5 ? "Dataset run description" : "",
|
||||
datasetId: dataset.id,
|
||||
|
||||
@@ -2,6 +2,8 @@ import {
|
||||
TraceRecordInsertType,
|
||||
ObservationRecordInsertType,
|
||||
ScoreRecordInsertType,
|
||||
DatasetRunItemRecordInsertType,
|
||||
createDatasetRunItemsCh,
|
||||
} from "../../../src/server";
|
||||
import { SEED_TEXT_PROMPTS } from "./postgres-seed-constants";
|
||||
import {
|
||||
@@ -42,6 +44,16 @@ export class ClickHouseQueryBuilder {
|
||||
return await createObservationsCh(observations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates INSERT query for dataset run items data using VALUES syntax.
|
||||
* Use for: Small datasets, dataset run items that link to postgres data (e.g. dataset runs)
|
||||
*/
|
||||
async executeDatasetRunItemsInsert(
|
||||
datasetRunItems: DatasetRunItemRecordInsertType[],
|
||||
): Promise<InsertResult> {
|
||||
return await createDatasetRunItemsCh(datasetRunItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates INSERT query for score data using VALUES syntax.
|
||||
* Use for: Small datasets, scores with custom values and metadata.
|
||||
|
||||
@@ -95,3 +95,57 @@ export const REALISTIC_METADATA_EXAMPLES = [
|
||||
},
|
||||
{ file_type: "JSON", validation: "passed", schema_version: "v1.2" },
|
||||
];
|
||||
|
||||
export const REALISTIC_AGENT_NAMES = [
|
||||
"AI-Coordinator",
|
||||
"TaskManager",
|
||||
"WorkflowOrchestrator",
|
||||
"PlanningAgent",
|
||||
"MultiStepAgent",
|
||||
"SmartCoordinator",
|
||||
];
|
||||
|
||||
export const REALISTIC_TOOL_NAMES = [
|
||||
"WebSearchTool",
|
||||
"CalculatorTool",
|
||||
"WeatherForecastTool",
|
||||
"EmailSenderTool",
|
||||
];
|
||||
|
||||
export const REALISTIC_CHAIN_NAMES = [
|
||||
"DataTransformationChain",
|
||||
"ProcessingPipeline",
|
||||
"TransformationChain",
|
||||
"MultiStepProcess",
|
||||
];
|
||||
|
||||
export const REALISTIC_RETRIEVER_NAMES = [
|
||||
"DocumentRetriever",
|
||||
"SemanticSearch",
|
||||
"InformationRetriever",
|
||||
"VectorSearch",
|
||||
"MemoryRetriever",
|
||||
];
|
||||
|
||||
export const REALISTIC_EVALUATOR_NAMES = [
|
||||
"QualityEvaluator",
|
||||
"RelevanceScorer",
|
||||
"AccuracyChecker",
|
||||
"ResponseEvaluator",
|
||||
"ContentScorer",
|
||||
];
|
||||
|
||||
export const REALISTIC_EMBEDDING_NAMES = [
|
||||
"TextEmbedding",
|
||||
"DocumentEncoder",
|
||||
"SemanticEncoder",
|
||||
"VectorEmbedding",
|
||||
];
|
||||
|
||||
export const REALISTIC_GUARDRAIL_NAMES = [
|
||||
"SafetyChecker",
|
||||
"ContentModerator",
|
||||
"ToxicityFilter",
|
||||
"JailbreakDetector",
|
||||
"PolicyEnforcer",
|
||||
];
|
||||
|
||||
@@ -4,8 +4,17 @@ import {
|
||||
REALISTIC_SPAN_NAMES,
|
||||
REALISTIC_GENERATION_NAMES,
|
||||
REALISTIC_MODELS,
|
||||
REALISTIC_AGENT_NAMES,
|
||||
REALISTIC_TOOL_NAMES,
|
||||
REALISTIC_CHAIN_NAMES,
|
||||
REALISTIC_RETRIEVER_NAMES,
|
||||
REALISTIC_EVALUATOR_NAMES,
|
||||
REALISTIC_EMBEDDING_NAMES,
|
||||
REALISTIC_GUARDRAIL_NAMES,
|
||||
} from "./clickhouse-seed-constants";
|
||||
import {
|
||||
generateDatasetItemId,
|
||||
generateDatasetRunItemId,
|
||||
generateDatasetRunTraceId,
|
||||
generateEvalObservationId,
|
||||
generateEvalScoreId,
|
||||
@@ -23,6 +32,8 @@ import {
|
||||
ObservationRecordInsertType,
|
||||
ScoreRecordInsertType,
|
||||
TraceRecordInsertType,
|
||||
DatasetRunItemRecordInsertType,
|
||||
createDatasetRunItem,
|
||||
} from "../../../src/server";
|
||||
|
||||
/**
|
||||
@@ -60,6 +71,49 @@ export class DataGenerator {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dataset run items for dataset runs.
|
||||
* Use for: Dataset experiment scenarios.
|
||||
*/
|
||||
generateDatasetRunItem(
|
||||
input: DatasetItemInput & { runCreatedAt: number },
|
||||
projectId: string,
|
||||
): DatasetRunItemRecordInsertType {
|
||||
const datasetRunItemId = generateDatasetRunItemId(
|
||||
input.datasetName,
|
||||
input.itemIndex,
|
||||
projectId,
|
||||
input.runNumber || 0,
|
||||
);
|
||||
|
||||
// TODO: there are too many dataset run items in the postgres database?
|
||||
return createDatasetRunItem({
|
||||
id: datasetRunItemId,
|
||||
project_id: projectId,
|
||||
trace_id: generateDatasetRunTraceId(
|
||||
input.datasetName,
|
||||
input.itemIndex,
|
||||
projectId,
|
||||
input.runNumber || 0,
|
||||
),
|
||||
dataset_id: `${input.datasetName}-${projectId.slice(-8)}`,
|
||||
dataset_run_id: `demo-dataset-run-${input.runNumber}-${projectId.slice(-8)}`,
|
||||
dataset_run_name: `demo-dataset-run-${input.runNumber}-${projectId.slice(-8)}`,
|
||||
dataset_run_created_at: input.runCreatedAt,
|
||||
dataset_run_description:
|
||||
(input.runNumber || 0) % 2 === 0 ? "Dataset run description" : "",
|
||||
dataset_run_metadata: { key: "value" },
|
||||
dataset_item_id: generateDatasetItemId(
|
||||
input.datasetName,
|
||||
input.itemIndex,
|
||||
projectId,
|
||||
input.runNumber || 0,
|
||||
),
|
||||
dataset_item_input: input.item.input,
|
||||
dataset_item_expected_output: input.item.output,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates traces from dataset items for experiment runs.
|
||||
* Use for: Dataset experiments scenarios.
|
||||
@@ -318,8 +372,56 @@ export class DataGenerator {
|
||||
const observations: ObservationRecordInsertType[] = [];
|
||||
|
||||
traces.forEach((trace, traceIndex) => {
|
||||
if (this.randomBoolean(0.1)) {
|
||||
const { observations: workflowObservations } =
|
||||
this.generateComprehensiveAIWorkflowTrace(trace.id, trace.project_id);
|
||||
observations.push(...workflowObservations);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < observationsPerTrace; i++) {
|
||||
const obsType = this.randomElement(["GENERATION", "SPAN", "EVENT"]);
|
||||
const obsType = this.randomBoolean(0.8) // More "traditional" types, are more common in app
|
||||
? this.randomElement(["GENERATION", "SPAN", "EVENT"])
|
||||
: this.randomElement([
|
||||
"AGENT",
|
||||
"TOOL",
|
||||
"CHAIN",
|
||||
"RETRIEVER",
|
||||
"EVALUATOR",
|
||||
"EMBEDDING",
|
||||
"GUARDRAIL",
|
||||
]);
|
||||
|
||||
let observationName: string;
|
||||
switch (obsType) {
|
||||
case "AGENT":
|
||||
observationName = this.randomElement(REALISTIC_AGENT_NAMES);
|
||||
break;
|
||||
case "TOOL":
|
||||
observationName = this.randomElement(REALISTIC_TOOL_NAMES);
|
||||
break;
|
||||
case "CHAIN":
|
||||
observationName = this.randomElement(REALISTIC_CHAIN_NAMES);
|
||||
break;
|
||||
case "RETRIEVER":
|
||||
observationName = this.randomElement(REALISTIC_RETRIEVER_NAMES);
|
||||
break;
|
||||
case "EVALUATOR":
|
||||
observationName = this.randomElement(REALISTIC_EVALUATOR_NAMES);
|
||||
break;
|
||||
case "EMBEDDING":
|
||||
observationName = this.randomElement(REALISTIC_EMBEDDING_NAMES);
|
||||
break;
|
||||
case "GUARDRAIL":
|
||||
observationName = this.randomElement(REALISTIC_GUARDRAIL_NAMES);
|
||||
break;
|
||||
case "GENERATION":
|
||||
observationName = this.randomElement(REALISTIC_GENERATION_NAMES);
|
||||
break;
|
||||
default:
|
||||
observationName = this.randomElement(REALISTIC_SPAN_NAMES);
|
||||
break;
|
||||
}
|
||||
|
||||
const observation: ObservationRecordInsertType = createObservation({
|
||||
id: `obs-synthetic-${traceIndex}-${i}`,
|
||||
@@ -328,28 +430,28 @@ export class DataGenerator {
|
||||
parent_observation_id:
|
||||
i > 0 ? `obs-synthetic-${traceIndex}-${i - 1}` : undefined,
|
||||
type: obsType as any,
|
||||
name:
|
||||
obsType === "GENERATION"
|
||||
? this.randomElement(REALISTIC_GENERATION_NAMES)
|
||||
: this.randomElement(REALISTIC_SPAN_NAMES),
|
||||
name: observationName,
|
||||
input:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" || obsType === "EMBEDDING"
|
||||
? this.generateObservationInput()
|
||||
: undefined,
|
||||
output:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" ||
|
||||
obsType === "RETRIEVER" ||
|
||||
obsType === "EVALUATOR" ||
|
||||
obsType === "GUARDRAIL"
|
||||
? this.generateObservationOutput()
|
||||
: undefined,
|
||||
provided_model_name:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" || obsType === "EMBEDDING"
|
||||
? this.randomElement(REALISTIC_MODELS)
|
||||
: undefined,
|
||||
model_parameters:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" || obsType === "EMBEDDING"
|
||||
? JSON.stringify({ temperature: 0.7 })
|
||||
: undefined,
|
||||
usage_details:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" || obsType === "EMBEDDING"
|
||||
? {
|
||||
input: this.randomInt(20, 200),
|
||||
output: this.randomInt(10, 100),
|
||||
@@ -357,7 +459,7 @@ export class DataGenerator {
|
||||
}
|
||||
: undefined,
|
||||
provided_usage_details:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" || obsType === "EMBEDDING"
|
||||
? {
|
||||
input: this.randomInt(20, 200),
|
||||
output: this.randomInt(10, 100),
|
||||
@@ -365,7 +467,7 @@ export class DataGenerator {
|
||||
}
|
||||
: undefined,
|
||||
cost_details:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" || obsType === "EMBEDDING"
|
||||
? {
|
||||
input: this.randomInt(1, 10) / 100000,
|
||||
output: this.randomInt(1, 20) / 100000,
|
||||
@@ -373,7 +475,7 @@ export class DataGenerator {
|
||||
}
|
||||
: undefined,
|
||||
provided_cost_details:
|
||||
obsType === "GENERATION"
|
||||
obsType === "GENERATION" || obsType === "EMBEDDING"
|
||||
? {
|
||||
input: this.randomInt(1, 10) / 100000,
|
||||
output: this.randomInt(1, 20) / 100000,
|
||||
@@ -453,6 +555,252 @@ export class DataGenerator {
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a workflow trace with all possible observation types.
|
||||
*/
|
||||
generateComprehensiveAIWorkflowTrace(
|
||||
traceId: string,
|
||||
projectId: string,
|
||||
): {
|
||||
trace: TraceRecordInsertType;
|
||||
observations: ObservationRecordInsertType[];
|
||||
} {
|
||||
// Create the main trace
|
||||
const trace = createTrace({
|
||||
id: traceId,
|
||||
project_id: projectId,
|
||||
name: "AI-Agent-Workflow",
|
||||
input:
|
||||
"Analyze and summarize the latest research papers on quantum computing",
|
||||
output:
|
||||
"Here is a comprehensive summary of quantum computing research trends with key insights and recommendations.",
|
||||
user_id: this.randomBoolean(0.3)
|
||||
? `user_${this.randomInt(1, 1000)}`
|
||||
: null,
|
||||
session_id: this.randomBoolean(0.3)
|
||||
? `session_${this.randomInt(1, 100)}`
|
||||
: undefined,
|
||||
environment: "default",
|
||||
metadata: { workflowType: "comprehensive-ai", purpose: "demonstration" },
|
||||
tags: ["ai-agent", "multi-step", "comprehensive"],
|
||||
public: true,
|
||||
bookmarked: this.randomBoolean(0.2),
|
||||
});
|
||||
|
||||
const observations: ObservationRecordInsertType[] = [];
|
||||
const baseTime = Date.now();
|
||||
|
||||
// 1. AGENT - Main coordinator
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-agent`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
type: "AGENT",
|
||||
name: this.randomElement(REALISTIC_AGENT_NAMES),
|
||||
input: "Plan and coordinate the research analysis workflow",
|
||||
output:
|
||||
"Workflow planned: retrieve documents → create embeddings → analyze → evaluate → check safety",
|
||||
start_time: baseTime,
|
||||
end_time: baseTime + 500,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
metadata: { role: "coordinator", step: "1" },
|
||||
}),
|
||||
);
|
||||
|
||||
// 2. RETRIEVER - Document retrieval
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-retriever`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
parent_observation_id: `${traceId}-agent`,
|
||||
type: "RETRIEVER",
|
||||
name: this.randomElement(REALISTIC_RETRIEVER_NAMES),
|
||||
input: "query: quantum computing research papers 2024",
|
||||
output: "Retrieved 15 relevant research papers from arXiv and IEEE",
|
||||
start_time: baseTime + 500,
|
||||
end_time: baseTime + 2000,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
metadata: { documentsFound: "15", sources: "arXiv,IEEE" },
|
||||
}),
|
||||
);
|
||||
|
||||
// 3. EMBEDDING - Create document embeddings
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-embedding`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
parent_observation_id: `${traceId}-retriever`,
|
||||
type: "EMBEDDING",
|
||||
name: this.randomElement(REALISTIC_EMBEDDING_NAMES),
|
||||
input: "15 research paper abstracts and titles",
|
||||
output: "Generated 1536-dimensional embeddings for semantic similarity",
|
||||
start_time: baseTime + 2000,
|
||||
end_time: baseTime + 3500,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
metadata: {
|
||||
embeddingModel: "text-embedding-ada-002",
|
||||
dimensions: "1536",
|
||||
},
|
||||
usage_details: {
|
||||
input: this.randomInt(2000, 4000),
|
||||
total: this.randomInt(2000, 4000),
|
||||
},
|
||||
provided_usage_details: {
|
||||
input: this.randomInt(2000, 4000),
|
||||
total: this.randomInt(2000, 4000),
|
||||
},
|
||||
cost_details: {
|
||||
input: this.randomInt(5, 15) / 100000,
|
||||
total: this.randomInt(5, 15) / 100000,
|
||||
},
|
||||
provided_cost_details: {
|
||||
input: this.randomInt(5, 15) / 100000,
|
||||
total: this.randomInt(5, 15) / 100000,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// 4. CHAIN - Processing pipeline
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-chain`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
parent_observation_id: `${traceId}-embedding`,
|
||||
type: "CHAIN",
|
||||
name: this.randomElement(REALISTIC_CHAIN_NAMES),
|
||||
input: "Research papers with embeddings",
|
||||
output:
|
||||
"Processed and analyzed 15 papers through multi-step analysis chain",
|
||||
start_time: baseTime + 3500,
|
||||
end_time: baseTime + 8000,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
metadata: { steps: "4", processed: "15" },
|
||||
}),
|
||||
);
|
||||
|
||||
// 5. TOOL - External API call for additional context
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-tool`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
parent_observation_id: `${traceId}-chain`,
|
||||
type: "TOOL",
|
||||
name: this.randomElement(REALISTIC_TOOL_NAMES),
|
||||
input: "Search for quantum computing market trends",
|
||||
output:
|
||||
"Market data: $1.2B industry, 25% YoY growth, key players identified",
|
||||
start_time: baseTime + 8000,
|
||||
end_time: baseTime + 10000,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
metadata: { toolType: "api-call", endpoint: "market-research" },
|
||||
}),
|
||||
);
|
||||
|
||||
// 6. GENERATION - Final summary generation
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-generation`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
parent_observation_id: `${traceId}-tool`,
|
||||
type: "GENERATION",
|
||||
name: this.randomElement(REALISTIC_GENERATION_NAMES),
|
||||
input:
|
||||
"Synthesize research analysis and market data into comprehensive summary",
|
||||
output:
|
||||
"Generated comprehensive 2000-word analysis of quantum computing research trends",
|
||||
provided_model_name: this.randomElement(REALISTIC_MODELS),
|
||||
model_parameters: JSON.stringify({
|
||||
temperature: 0.3,
|
||||
max_tokens: 2000,
|
||||
}),
|
||||
start_time: baseTime + 10000,
|
||||
end_time: baseTime + 15000,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
usage_details: {
|
||||
input: this.randomInt(1500, 2500),
|
||||
output: this.randomInt(1800, 2200),
|
||||
total: this.randomInt(3300, 4700),
|
||||
},
|
||||
provided_usage_details: {
|
||||
input: this.randomInt(1500, 2500),
|
||||
output: this.randomInt(1800, 2200),
|
||||
total: this.randomInt(3300, 4700),
|
||||
},
|
||||
cost_details: {
|
||||
input: this.randomInt(15, 25) / 100000,
|
||||
output: this.randomInt(35, 45) / 100000,
|
||||
total: this.randomInt(50, 70) / 100000,
|
||||
},
|
||||
provided_cost_details: {
|
||||
input: this.randomInt(15, 25) / 100000,
|
||||
output: this.randomInt(35, 45) / 100000,
|
||||
total: this.randomInt(50, 70) / 100000,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// 7. EVALUATOR - Quality evaluation
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-evaluator`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
parent_observation_id: `${traceId}-generation`,
|
||||
type: "EVALUATOR",
|
||||
name: this.randomElement(REALISTIC_EVALUATOR_NAMES),
|
||||
input: "Evaluate summary quality, accuracy, and completeness",
|
||||
output: "Quality score: 8.7/10, High accuracy, Comprehensive coverage",
|
||||
start_time: baseTime + 15000,
|
||||
end_time: baseTime + 16500,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
metadata: {
|
||||
qualityScore: "8.7",
|
||||
accuracy: "high",
|
||||
completeness: "comprehensive",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// 8. GUARDRAIL - Safety and compliance check
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: `${traceId}-guardrail`,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
parent_observation_id: `${traceId}-evaluator`,
|
||||
type: "GUARDRAIL",
|
||||
name: this.randomElement(REALISTIC_GUARDRAIL_NAMES),
|
||||
input: "Check content for safety, bias, and compliance issues",
|
||||
output:
|
||||
"✓ Content approved: No safety issues, Low bias detected, Compliant",
|
||||
start_time: baseTime + 16500,
|
||||
end_time: baseTime + 17000,
|
||||
level: "DEFAULT",
|
||||
environment: trace.environment,
|
||||
metadata: {
|
||||
safetyCheck: "passed",
|
||||
biasLevel: "low",
|
||||
compliance: "approved",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return { trace, observations };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates evaluation traces for testing evaluator configurations.
|
||||
* Use for: Evaluation testing, score validation, evaluator development.
|
||||
|
||||
@@ -11,29 +11,29 @@
|
||||
## 🎯 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
\`\`\`bash
|
||||
npm install @langfuse/core
|
||||
pip install langfuse
|
||||
\`\`\`bash
|
||||
npm install @langfuse/core
|
||||
pip install langfuse
|
||||
\`\`\`
|
||||
|
||||
### Quick Setup
|
||||
1. **Initialize your project**
|
||||
\`\`\`typescript
|
||||
import { Langfuse } from 'langfuse'
|
||||
|
||||
const langfuse = new Langfuse({
|
||||
secretKey: process.env.LANGFUSE_SECRET_KEY,
|
||||
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
||||
baseUrl: 'https://cloud.langfuse.com'
|
||||
})
|
||||
\`\`\`typescript
|
||||
import { Langfuse } from 'langfuse'
|
||||
|
||||
const langfuse = new Langfuse({
|
||||
secretKey: process.env.LANGFUSE_SECRET_KEY,
|
||||
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
||||
baseUrl: 'https://cloud.langfuse.com'
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
2. **Create your first trace**
|
||||
\`\`\`python
|
||||
from langfuse import Langfuse
|
||||
|
||||
langfuse = Langfuse()
|
||||
trace = langfuse.trace(name="chat-application")
|
||||
\`\`\`python
|
||||
from langfuse import Langfuse
|
||||
|
||||
langfuse = Langfuse()
|
||||
trace = langfuse.trace(name="chat-application")
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
@@ -72,75 +72,75 @@ graph TD
|
||||
> **Note:** Traces are the foundation of observability in LLM applications.
|
||||
|
||||
#### Creating Traces
|
||||
\`\`\`typescript
|
||||
// Basic trace creation
|
||||
const trace = langfuse.trace({
|
||||
name: "user-query-processing",
|
||||
userId: "user-123",
|
||||
sessionId: "session-456",
|
||||
metadata: {
|
||||
environment: "production",
|
||||
version: "2.1.0"
|
||||
}
|
||||
})
|
||||
\`\`\`typescript
|
||||
// Basic trace creation
|
||||
const trace = langfuse.trace({
|
||||
name: "user-query-processing",
|
||||
userId: "user-123",
|
||||
sessionId: "session-456",
|
||||
metadata: {
|
||||
environment: "production",
|
||||
version: "2.1.0"
|
||||
}
|
||||
})
|
||||
|
||||
// Nested observations
|
||||
const span = trace.span({
|
||||
name: "document-retrieval",
|
||||
input: { query: "What is machine learning?" },
|
||||
metadata: { vectorStore: "pinecone" }
|
||||
})
|
||||
// Nested observations
|
||||
const span = trace.span({
|
||||
name: "document-retrieval",
|
||||
input: { query: "What is machine learning?" },
|
||||
metadata: { vectorStore: "pinecone" }
|
||||
})
|
||||
|
||||
const generation = span.generation({
|
||||
name: "answer-generation",
|
||||
model: "gpt-4",
|
||||
input: retrievedDocs,
|
||||
output: generatedAnswer,
|
||||
usage: {
|
||||
promptTokens: 1250,
|
||||
completionTokens: 420,
|
||||
totalTokens: 1670
|
||||
}
|
||||
})
|
||||
const generation = span.generation({
|
||||
name: "answer-generation",
|
||||
model: "gpt-4",
|
||||
input: retrievedDocs,
|
||||
output: generatedAnswer,
|
||||
usage: {
|
||||
promptTokens: 1250,
|
||||
completionTokens: 420,
|
||||
totalTokens: 1670
|
||||
}
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
### Advanced Features
|
||||
|
||||
#### 🔄 Async Processing
|
||||
\`\`\`python
|
||||
import asyncio
|
||||
from langfuse import Langfuse
|
||||
\`\`\`python
|
||||
import asyncio
|
||||
from langfuse import Langfuse
|
||||
|
||||
async def process_batch():
|
||||
langfuse = Langfuse()
|
||||
|
||||
tasks = []
|
||||
for item in batch_items:
|
||||
task = asyncio.create_task(
|
||||
process_item_with_tracing(langfuse, item)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
return results
|
||||
async def process_batch():
|
||||
langfuse = Langfuse()
|
||||
|
||||
tasks = []
|
||||
for item in batch_items:
|
||||
task = asyncio.create_task(
|
||||
process_item_with_tracing(langfuse, item)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
return results
|
||||
\`\`\`
|
||||
|
||||
#### 🎯 Custom Scoring
|
||||
\`\`\`typescript
|
||||
// Automated scoring
|
||||
trace.score({
|
||||
name: "relevance",
|
||||
value: 0.95,
|
||||
comment: "Highly relevant response"
|
||||
})
|
||||
\`\`\`typescript
|
||||
// Automated scoring
|
||||
trace.score({
|
||||
name: "relevance",
|
||||
value: 0.95,
|
||||
comment: "Highly relevant response"
|
||||
})
|
||||
|
||||
// Human feedback scoring
|
||||
trace.score({
|
||||
name: "user-satisfaction",
|
||||
value: 1,
|
||||
source: "user-feedback",
|
||||
comment: "User rated 5/5 stars"
|
||||
})
|
||||
// Human feedback scoring
|
||||
trace.score({
|
||||
name: "user-satisfaction",
|
||||
value: 1,
|
||||
source: "user-feedback",
|
||||
comment: "User rated 5/5 stars"
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
@@ -156,20 +156,20 @@ trace.score({
|
||||
- **User Satisfaction**: Quality metrics
|
||||
|
||||
#### Dashboard Setup
|
||||
\`\`\`yaml
|
||||
# monitoring-config.yml
|
||||
dashboards:
|
||||
- name: "LLM Performance"
|
||||
panels:
|
||||
- type: "time-series"
|
||||
title: "Response Latency"
|
||||
query: "avg(response_time) by (model)"
|
||||
- type: "stat"
|
||||
title: "Daily Token Usage"
|
||||
query: "sum(tokens_used)"
|
||||
- type: "table"
|
||||
title: "Top Errors"
|
||||
query: "topk(10, count by (error_type))"
|
||||
\`\`\`yaml
|
||||
# monitoring-config.yml
|
||||
dashboards:
|
||||
- name: "LLM Performance"
|
||||
panels:
|
||||
- type: "time-series"
|
||||
title: "Response Latency"
|
||||
query: "avg(response_time) by (model)"
|
||||
- type: "stat"
|
||||
title: "Daily Token Usage"
|
||||
query: "sum(tokens_used)"
|
||||
- type: "table"
|
||||
title: "Top Errors"
|
||||
query: "topk(10, count by (error_type))"
|
||||
\`\`\`
|
||||
|
||||
### 🔐 Security Considerations
|
||||
@@ -177,40 +177,40 @@ dashboards:
|
||||
> ⚠️ **Important**: Never log sensitive user data in traces
|
||||
|
||||
#### Data Sanitization
|
||||
\`\`\`python
|
||||
def sanitize_input(data):
|
||||
"""Remove PII from trace data"""
|
||||
sanitized = data.copy()
|
||||
|
||||
# Remove email addresses
|
||||
sanitized = re.sub(r'\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b',
|
||||
'[EMAIL_REDACTED]', sanitized)
|
||||
|
||||
# Remove phone numbers
|
||||
sanitized = re.sub(r'\\b\\d{3}-\\d{3}-\\d{4}\\b',
|
||||
'[PHONE_REDACTED]', sanitized)
|
||||
|
||||
return sanitized
|
||||
\`\`\`python
|
||||
def sanitize_input(data):
|
||||
"""Remove PII from trace data"""
|
||||
sanitized = data.copy()
|
||||
|
||||
# Remove email addresses
|
||||
sanitized = re.sub(r'\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b',
|
||||
'[EMAIL_REDACTED]', sanitized)
|
||||
|
||||
# Remove phone numbers
|
||||
sanitized = re.sub(r'\\b\\d{3}-\\d{3}-\\d{4}\\b',
|
||||
'[PHONE_REDACTED]', sanitized)
|
||||
|
||||
return sanitized
|
||||
\`\`\`
|
||||
|
||||
### 🚀 Performance Optimization
|
||||
|
||||
#### Batch Processing
|
||||
\`\`\`typescript
|
||||
// Efficient batch uploads
|
||||
const batchSize = 100
|
||||
const traces = []
|
||||
\`\`\`typescript
|
||||
// Efficient batch uploads
|
||||
const batchSize = 100
|
||||
const traces = []
|
||||
|
||||
for (let i = 0; i < data.length; i += batchSize) {
|
||||
const batch = data.slice(i, i + batchSize)
|
||||
const processedBatch = await Promise.all(
|
||||
batch.map(item => processWithLangfuse(item))
|
||||
)
|
||||
traces.push(...processedBatch)
|
||||
}
|
||||
for (let i = 0; i < data.length; i += batchSize) {
|
||||
const batch = data.slice(i, i + batchSize)
|
||||
const processedBatch = await Promise.all(
|
||||
batch.map(item => processWithLangfuse(item))
|
||||
)
|
||||
traces.push(...processedBatch)
|
||||
}
|
||||
|
||||
// Flush all traces at once
|
||||
await langfuse.flushAsync()
|
||||
// Flush all traces at once
|
||||
await langfuse.flushAsync()
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
@@ -219,34 +219,34 @@ await langfuse.flushAsync()
|
||||
|
||||
### Multi-Agent System Tracing
|
||||
\`\`\`python
|
||||
class MultiAgentTracer:
|
||||
def __init__(self):
|
||||
self.langfuse = Langfuse()
|
||||
|
||||
async def orchestrate_agents(self, task):
|
||||
# Main orchestration trace
|
||||
main_trace = self.langfuse.trace(
|
||||
name="multi-agent-orchestration",
|
||||
input={"task": task}
|
||||
)
|
||||
|
||||
# Agent 1: Research
|
||||
research_span = main_trace.span(name="research-agent")
|
||||
research_result = await self.research_agent.process(task)
|
||||
research_span.end(output=research_result)
|
||||
|
||||
class MultiAgentTracer:
|
||||
def __init__(self):
|
||||
self.langfuse = Langfuse()
|
||||
|
||||
async def orchestrate_agents(self, task):
|
||||
# Main orchestration trace
|
||||
main_trace = self.langfuse.trace(
|
||||
name="multi-agent-orchestration",
|
||||
input={"task": task}
|
||||
)
|
||||
|
||||
# Agent 1: Research
|
||||
research_span = main_trace.span(name="research-agent")
|
||||
research_result = await self.research_agent.process(task)
|
||||
research_span.end(output=research_result)
|
||||
|
||||
# Agent 2: Analysis
|
||||
analysis_span = main_trace.span(name="analysis-agent")
|
||||
analysis_result = await self.analysis_agent.process(research_result)
|
||||
analysis_span.end(output=analysis_result)
|
||||
|
||||
# Agent 3: Synthesis
|
||||
synthesis_span = main_trace.span(name="synthesis-agent")
|
||||
final_result = await self.synthesis_agent.process(analysis_result)
|
||||
synthesis_span.end(output=final_result)
|
||||
|
||||
main_trace.end(output=final_result)
|
||||
return final_result
|
||||
analysis_span = main_trace.span(name="analysis-agent")
|
||||
analysis_result = await self.analysis_agent.process(research_result)
|
||||
analysis_span.end(output=analysis_result)
|
||||
|
||||
# Agent 3: Synthesis
|
||||
synthesis_span = main_trace.span(name="synthesis-agent")
|
||||
final_result = await self.synthesis_agent.process(analysis_result)
|
||||
synthesis_span.end(output=final_result)
|
||||
|
||||
main_trace.end(output=final_result)
|
||||
return final_result
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
@@ -256,11 +256,11 @@ class MultiAgentTracer:
|
||||
With proper implementation of Langfuse tracing, you can:
|
||||
|
||||
- ✅ **Monitor** your LLM applications in real-time
|
||||
- ✅ **Debug** issues with detailed trace information
|
||||
- ✅ **Debug** issues with detailed trace information
|
||||
- ✅ **Optimize** performance and costs
|
||||
- ✅ **Scale** your applications with confidence
|
||||
|
||||
### Next Steps
|
||||
1. Review the [official documentation](https://langfuse.com/docs)
|
||||
2. Join our [Discord community](https://discord.gg/langfuse)
|
||||
3. Check out [example projects](https://github.com/langfuse/langfuse)
|
||||
3. Check out [example projects](https://github.com/langfuse/langfuse)
|
||||
|
||||
@@ -271,6 +271,68 @@ export const SEED_TEXT_PROMPTS = [
|
||||
labels: ["production", "latest"],
|
||||
tags: ["tag1", "tag2"],
|
||||
},
|
||||
{
|
||||
id: `prompt-with-many-labels`,
|
||||
createdBy: "user-1",
|
||||
prompt:
|
||||
"This is a comprehensive prompt for testing multiple label scenarios. It demonstrates how prompts can be tagged with numerous labels for organization, categorization, and filtering purposes. Use this prompt to understand how label management works at scale. Variables: {{input}}",
|
||||
name: "prompt-with-many-labels",
|
||||
version: 1,
|
||||
labels: [
|
||||
"production",
|
||||
"latest",
|
||||
"v1",
|
||||
"v2",
|
||||
"stable",
|
||||
"beta",
|
||||
"alpha",
|
||||
"test",
|
||||
"development",
|
||||
"staging",
|
||||
"experimental",
|
||||
"feature",
|
||||
"bugfix",
|
||||
"hotfix",
|
||||
"critical",
|
||||
"high-priority",
|
||||
"medium-priority",
|
||||
"low-priority",
|
||||
"urgent",
|
||||
"customer-facing",
|
||||
"internal",
|
||||
"public",
|
||||
"private",
|
||||
"confidential",
|
||||
"ai",
|
||||
"nlp",
|
||||
"chatbot",
|
||||
"assistant",
|
||||
"automation",
|
||||
"ml",
|
||||
"data",
|
||||
"analytics",
|
||||
"monitoring",
|
||||
"logging",
|
||||
"debug",
|
||||
"performance",
|
||||
"security",
|
||||
"compliance",
|
||||
"audit",
|
||||
"review",
|
||||
"approved",
|
||||
"rejected",
|
||||
"pending",
|
||||
"archived",
|
||||
"deprecated",
|
||||
"legacy",
|
||||
"migration",
|
||||
"upgrade",
|
||||
"downgrade",
|
||||
"template",
|
||||
"example",
|
||||
],
|
||||
tags: [],
|
||||
},
|
||||
];
|
||||
|
||||
export const SEED_CHAT_ML_PROMPTS = [
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
export const generateDatasetRunItemId = (
|
||||
datasetName: string,
|
||||
itemIndex: number,
|
||||
projectId: string,
|
||||
runNumber: number,
|
||||
) => {
|
||||
return `dataset-run-item-${datasetName}-${itemIndex}-${projectId.slice(-8)}-${runNumber}`;
|
||||
};
|
||||
|
||||
export const generateDatasetItemId = (
|
||||
datasetName: string,
|
||||
itemIndex: number,
|
||||
projectId: string,
|
||||
runNumber: number,
|
||||
) => {
|
||||
return `dataset-item-${datasetName}-${itemIndex}-${projectId.slice(-8)}-${runNumber}`;
|
||||
};
|
||||
|
||||
export const generateDatasetRunTraceId = (
|
||||
datasetName: string,
|
||||
itemIndex: number,
|
||||
|
||||
@@ -92,12 +92,26 @@ export class SeederOrchestrator {
|
||||
logger.info(
|
||||
`Processing run ${runNumber + 1}/${numberOfRuns} for project ${projectId}`,
|
||||
);
|
||||
// const now = Date.now();
|
||||
|
||||
const traces: TraceRecordInsertType[] = [];
|
||||
const observations: ObservationRecordInsertType[] = [];
|
||||
// const datasetRunItems: DatasetRunItemRecordInsertType[] = [];
|
||||
|
||||
for (const seedDataset of SEED_DATASETS) {
|
||||
for (const [itemIndex, datasetItem] of seedDataset.items.entries()) {
|
||||
// // Generate dataset run item data
|
||||
// const datasetRunItem = this.dataGenerator.generateDatasetRunItem(
|
||||
// {
|
||||
// datasetName: seedDataset.name,
|
||||
// itemIndex,
|
||||
// item: datasetItem,
|
||||
// runNumber,
|
||||
// runCreatedAt: now,
|
||||
// },
|
||||
// projectId,
|
||||
// );
|
||||
|
||||
// Generate trace data
|
||||
const trace = this.dataGenerator.generateDatasetTrace(
|
||||
{
|
||||
@@ -123,12 +137,14 @@ export class SeederOrchestrator {
|
||||
|
||||
traces.push(trace);
|
||||
observations.push(observation);
|
||||
// datasetRunItems.push(datasetRunItem);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.queryBuilder.executeTracesInsert(traces);
|
||||
await this.queryBuilder.executeObservationsInsert(observations);
|
||||
// await this.queryBuilder.executeDatasetRunItemsInsert(datasetRunItems);
|
||||
} catch (error) {
|
||||
logger.error(`✗ Insert failed:`, error);
|
||||
throw error;
|
||||
|
||||
@@ -88,6 +88,7 @@ declare const globalThis: {
|
||||
kyselyPrismaGlobal: { $kysely: Kysely<DB> } | undefined;
|
||||
} & typeof global;
|
||||
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
globalThis.prismaGlobal ??= createPrismaInstance(); // regular instantiation
|
||||
globalThis.kyselyPrismaGlobal ??= globalThis.prismaGlobal.$extends(
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import { Action, Trigger } from "@prisma/client";
|
||||
import { FilterState } from "../types";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export enum TriggerEventSource {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Prompt = "prompt",
|
||||
}
|
||||
|
||||
export const EventActionSchema = z.enum(["created", "updated", "deleted"]);
|
||||
|
||||
export type TriggerEventAction = z.infer<typeof EventActionSchema>;
|
||||
|
||||
export const TriggerEventSourceSchema = z.enum([TriggerEventSource.Prompt]);
|
||||
|
||||
export type TriggerDomain = Omit<
|
||||
Trigger,
|
||||
"filter" | "eventSource" | "eventActions"
|
||||
> & {
|
||||
filter: FilterState;
|
||||
eventSource: TriggerEventSource;
|
||||
eventActions: TriggerEventAction[];
|
||||
};
|
||||
|
||||
export type AutomationDomain = {
|
||||
id: string;
|
||||
name: string;
|
||||
trigger: TriggerDomain;
|
||||
action: ActionDomain;
|
||||
};
|
||||
|
||||
export type ActionDomain = Omit<Action, "config"> & {
|
||||
config: SafeActionConfig;
|
||||
};
|
||||
|
||||
export type ActionDomainWithSecrets = Omit<Action, "config"> & {
|
||||
config: ActionConfigWithSecrets;
|
||||
};
|
||||
|
||||
export const ActionTypeSchema = z.enum(["WEBHOOK", "SLACK"]);
|
||||
|
||||
export const AvailableWebhookApiSchema = z.record(
|
||||
z.enum(["prompt"]),
|
||||
z.enum(["v1"]),
|
||||
);
|
||||
|
||||
export const RequestHeaderSchema = z.object({
|
||||
secret: z.boolean(),
|
||||
value: z.string(),
|
||||
});
|
||||
|
||||
export const WebhookActionConfigSchema = z.object({
|
||||
type: z.literal("WEBHOOK"),
|
||||
url: z.url(),
|
||||
headers: z.record(z.string(), z.string()).optional(), // deprecated field, use requestHeaders instead
|
||||
requestHeaders: z.record(z.string(), RequestHeaderSchema).optional(), // might not exist on legacy webhooks
|
||||
displayHeaders: z.record(z.string(), RequestHeaderSchema).optional(), // might not exist on legacy webhooks
|
||||
apiVersion: AvailableWebhookApiSchema,
|
||||
secretKey: z.string(),
|
||||
displaySecretKey: z.string(),
|
||||
lastFailingExecutionId: z.string().nullish(),
|
||||
});
|
||||
|
||||
export const SafeWebhookActionConfigSchema = WebhookActionConfigSchema.omit({
|
||||
secretKey: true,
|
||||
headers: true,
|
||||
requestHeaders: true,
|
||||
});
|
||||
|
||||
export type SafeWebhookActionConfig = z.infer<
|
||||
typeof SafeWebhookActionConfigSchema
|
||||
>;
|
||||
|
||||
export const WebhookActionCreateSchema = WebhookActionConfigSchema.omit({
|
||||
secretKey: true,
|
||||
displaySecretKey: true,
|
||||
headers: true, // don't use legacy field anymore
|
||||
displayHeaders: true,
|
||||
});
|
||||
|
||||
export const SlackActionConfigSchema = z.object({
|
||||
type: z.literal("SLACK"),
|
||||
channelId: z.string(),
|
||||
channelName: z.string(),
|
||||
messageTemplate: z.string().optional(),
|
||||
});
|
||||
|
||||
export type SlackActionConfig = z.infer<typeof SlackActionConfigSchema>;
|
||||
|
||||
export const ActionConfigSchema = z.discriminatedUnion("type", [
|
||||
WebhookActionConfigSchema,
|
||||
SlackActionConfigSchema,
|
||||
]);
|
||||
|
||||
export const ActionCreateSchema = z.discriminatedUnion("type", [
|
||||
WebhookActionCreateSchema,
|
||||
SlackActionConfigSchema,
|
||||
]);
|
||||
|
||||
export const SafeActionConfigSchema = z.discriminatedUnion("type", [
|
||||
SafeWebhookActionConfigSchema,
|
||||
SlackActionConfigSchema,
|
||||
]);
|
||||
|
||||
export type ActionTypes = z.infer<typeof ActionTypeSchema>;
|
||||
export type ActionConfig = z.infer<typeof ActionConfigSchema>;
|
||||
export type ActionCreate = z.infer<typeof ActionCreateSchema>;
|
||||
export type SafeActionConfig = z.infer<typeof SafeActionConfigSchema>;
|
||||
|
||||
export type WebhookActionCreate = z.infer<typeof WebhookActionCreateSchema>;
|
||||
export type WebhookActionConfigWithSecrets = z.infer<
|
||||
typeof WebhookActionConfigSchema
|
||||
>;
|
||||
|
||||
export type ActionConfigWithSecrets = z.infer<typeof ActionConfigSchema>;
|
||||
|
||||
// Type Guards for Runtime Validation
|
||||
// Using existing Zod schemas to provide both compile-time and runtime type safety
|
||||
|
||||
/**
|
||||
* Type guard to check if a config is a valid webhook configuration with secrets
|
||||
*/
|
||||
export function isWebhookActionConfig(
|
||||
config: unknown,
|
||||
): config is WebhookActionConfigWithSecrets {
|
||||
return WebhookActionConfigSchema.safeParse(config).success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a config is a valid Slack configuration
|
||||
*/
|
||||
export function isSlackActionConfig(
|
||||
config: unknown,
|
||||
): config is SlackActionConfig {
|
||||
return SlackActionConfigSchema.safeParse(config).success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an entire action has valid webhook configuration
|
||||
*/
|
||||
export function isWebhookAction(action: {
|
||||
type: string;
|
||||
config: unknown;
|
||||
}): action is { type: "WEBHOOK"; config: WebhookActionConfigWithSecrets } {
|
||||
return action.type === "WEBHOOK" && isWebhookActionConfig(action.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for safe webhook config (without secrets)
|
||||
*/
|
||||
export function isSafeWebhookActionConfig(
|
||||
config: unknown,
|
||||
): config is SafeWebhookActionConfig {
|
||||
return SafeWebhookActionConfigSchema.safeParse(config).success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts webhook config with secrets to safe config by only including allowed fields
|
||||
*/
|
||||
export function convertToSafeWebhookConfig(
|
||||
webhookConfig: WebhookActionConfigWithSecrets,
|
||||
): SafeWebhookActionConfig {
|
||||
return {
|
||||
type: webhookConfig.type,
|
||||
url: webhookConfig.url,
|
||||
displayHeaders: webhookConfig.displayHeaders,
|
||||
apiVersion: webhookConfig.apiVersion,
|
||||
displaySecretKey: webhookConfig.displaySecretKey,
|
||||
lastFailingExecutionId: webhookConfig.lastFailingExecutionId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import z from "zod/v4";
|
||||
import { jsonSchema } from "../utils/zod";
|
||||
import { MetadataDomain } from "./traces";
|
||||
|
||||
export const DatasetRunItemSchema = z.object({
|
||||
id: z.string(),
|
||||
projectId: z.string(),
|
||||
datasetRunId: z.string(),
|
||||
datasetItemId: z.string(),
|
||||
datasetId: z.string(),
|
||||
traceId: z.string(),
|
||||
observationId: z.string().nullable(),
|
||||
error: z.string().nullable(),
|
||||
// timestamps
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
// dataset run fields
|
||||
datasetRunName: z.string(),
|
||||
datasetRunDescription: z.string().nullable(),
|
||||
datasetRunMetadata: MetadataDomain,
|
||||
datasetRunCreatedAt: z.date(),
|
||||
// dataset item fields
|
||||
datasetItemInput: jsonSchema,
|
||||
datasetItemExpectedOutput: jsonSchema,
|
||||
datasetItemMetadata: MetadataDomain,
|
||||
});
|
||||
|
||||
export type DatasetRunItemDomain = z.infer<typeof DatasetRunItemSchema>;
|
||||
@@ -2,3 +2,6 @@ export * from "./observations";
|
||||
export * from "./traces";
|
||||
export * from "./scores";
|
||||
export * from "./table-view-presets";
|
||||
export * from "./automations";
|
||||
export * from "./webhooks";
|
||||
export * from "./prompts";
|
||||
|
||||
@@ -6,8 +6,27 @@ export const ObservationType = {
|
||||
SPAN: "SPAN",
|
||||
EVENT: "EVENT",
|
||||
GENERATION: "GENERATION",
|
||||
AGENT: "AGENT",
|
||||
TOOL: "TOOL",
|
||||
CHAIN: "CHAIN",
|
||||
RETRIEVER: "RETRIEVER",
|
||||
EVALUATOR: "EVALUATOR",
|
||||
EMBEDDING: "EMBEDDING",
|
||||
GUARDRAIL: "GUARDRAIL",
|
||||
} as const;
|
||||
export const ObservationTypeDomain = z.enum(["SPAN", "EVENT", "GENERATION"]);
|
||||
|
||||
export const ObservationTypeDomain = z.enum([
|
||||
"SPAN",
|
||||
"EVENT",
|
||||
"GENERATION",
|
||||
"AGENT",
|
||||
"TOOL",
|
||||
"CHAIN",
|
||||
"RETRIEVER",
|
||||
"EVALUATOR",
|
||||
"EMBEDDING",
|
||||
"GUARDRAIL",
|
||||
]);
|
||||
export type ObservationType = z.infer<typeof ObservationTypeDomain>;
|
||||
|
||||
export const ObservationLevel = {
|
||||
@@ -74,3 +93,29 @@ export const ObservationSchema = z.object({
|
||||
});
|
||||
|
||||
export type Observation = z.infer<typeof ObservationSchema>;
|
||||
|
||||
/**
|
||||
* Returns true if an observation type is generation-like, meaning it could include LLM calls
|
||||
* and potentially has similar input/output fields.
|
||||
*/
|
||||
export const GenerationLikeObservationTypes = [
|
||||
ObservationType.GENERATION,
|
||||
ObservationType.AGENT,
|
||||
ObservationType.TOOL,
|
||||
ObservationType.CHAIN,
|
||||
ObservationType.RETRIEVER,
|
||||
ObservationType.EVALUATOR,
|
||||
ObservationType.EMBEDDING,
|
||||
ObservationType.GUARDRAIL,
|
||||
] as const;
|
||||
|
||||
export const isGenerationLike = (observationType: ObservationType): boolean => {
|
||||
return GenerationLikeObservationTypes.includes(observationType as any);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all generation-like observation types for use in filters and queries.
|
||||
*/
|
||||
export const getGenerationLikeTypes = (): ObservationType[] => {
|
||||
return [...GenerationLikeObservationTypes];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { z } from "zod/v4";
|
||||
import { jsonSchemaNullable } from "../utils/zod";
|
||||
|
||||
export const PromptDomainSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
version: z.number(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
createdBy: z.string(),
|
||||
isActive: z.boolean().nullable(),
|
||||
type: z.string().default("text"),
|
||||
tags: z.array(z.string()).default([]),
|
||||
labels: z.array(z.string()).default([]),
|
||||
prompt: jsonSchemaNullable,
|
||||
config: jsonSchemaNullable,
|
||||
projectId: z.string(),
|
||||
commitMessage: z.string().nullable(),
|
||||
});
|
||||
|
||||
export type PromptDomain = z.infer<typeof PromptDomainSchema>;
|
||||
@@ -3,11 +3,11 @@ import { orderBy } from "../interfaces/orderBy";
|
||||
import z from "zod/v4";
|
||||
|
||||
export enum TableViewPresetTableName {
|
||||
Traces = "traces",
|
||||
Observations = "observations",
|
||||
Scores = "scores",
|
||||
Sessions = "sessions",
|
||||
Datasets = "datasets",
|
||||
Traces = "traces", // eslint-disable-line no-unused-vars
|
||||
Observations = "observations", // eslint-disable-line no-unused-vars
|
||||
Scores = "scores", // eslint-disable-line no-unused-vars
|
||||
Sessions = "sessions", // eslint-disable-line no-unused-vars
|
||||
Datasets = "datasets", // eslint-disable-line no-unused-vars
|
||||
}
|
||||
|
||||
const TableViewPresetDomainSchema = z.object({
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { z } from "zod/v4";
|
||||
import { jsonSchema } from "../utils/zod";
|
||||
import { EventActionSchema } from "./automations";
|
||||
|
||||
export const WebhookDefaultHeaders = {
|
||||
"content-type": "application/json",
|
||||
"user-agent": "Langfuse/1.0",
|
||||
};
|
||||
|
||||
export const WebhookOutboundBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
timestamp: z.coerce.date(),
|
||||
type: z.literal("prompt-version"),
|
||||
apiVersion: z.literal("v1"),
|
||||
action: EventActionSchema,
|
||||
});
|
||||
|
||||
export const PromptWebhookOutboundSchema = z
|
||||
.object({
|
||||
prompt: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
version: z.number(),
|
||||
projectId: z.string(),
|
||||
labels: z.array(z.string()),
|
||||
prompt: jsonSchema.nullable(),
|
||||
type: z.string(),
|
||||
config: z.record(z.string(), z.any()),
|
||||
commitMessage: z.string().nullable(),
|
||||
tags: z.array(z.string()),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
}),
|
||||
})
|
||||
.and(WebhookOutboundBaseSchema);
|
||||
|
||||
export type PromptWebhookOutput = z.infer<typeof PromptWebhookOutboundSchema>;
|
||||
@@ -0,0 +1,60 @@
|
||||
import crypto from "crypto";
|
||||
import { env } from "../env";
|
||||
|
||||
const ENCRYPTION_KEY: string | undefined = env.ENCRYPTION_KEY; // Must be 256 bits (32 bytes, 64 hex characters)
|
||||
const IV_LENGTH: number = 12; // For AES-GCM, this is always 12
|
||||
|
||||
// Alternatively: openssl rand -hex 32
|
||||
export function keyGen() {
|
||||
return crypto.randomBytes(32).toString("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the given plain text using AES-256-GCM algorithm.
|
||||
*
|
||||
* @param {string} plainText - The text to encrypt.
|
||||
* @returns {string} The encrypted data in hex format, including IV and authentication tag.
|
||||
*/
|
||||
export function encrypt(plainText: string): string {
|
||||
if (!ENCRYPTION_KEY) {
|
||||
throw new Error("Missing environment variable: `ENCRYPTION_KEY`");
|
||||
}
|
||||
const iv = crypto.randomBytes(IV_LENGTH); // Directly use Buffer returned by randomBytes
|
||||
const cipher = crypto.createCipheriv(
|
||||
"aes-256-gcm",
|
||||
Buffer.from(ENCRYPTION_KEY, "hex"),
|
||||
iv,
|
||||
);
|
||||
let encrypted = cipher.update(plainText, "utf8", "hex");
|
||||
encrypted += cipher.final("hex");
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
// Return iv, encrypted data, and authTag as hex, combined in one line
|
||||
return iv.toString("hex") + ":" + encrypted + ":" + authTag.toString("hex");
|
||||
}
|
||||
|
||||
export function decrypt(text: string): string {
|
||||
if (!ENCRYPTION_KEY) {
|
||||
throw new Error("Missing environment variable: `ENCRYPTION_KEY`");
|
||||
}
|
||||
const [ivHex, encryptedHex, authTagHex] = text.split(":");
|
||||
if (!ivHex || !encryptedHex || !authTagHex) {
|
||||
throw new Error("Invalid or corrupted cipher format");
|
||||
}
|
||||
|
||||
const iv = Buffer.from(ivHex, "hex");
|
||||
const encryptedText = Buffer.from(encryptedHex, "hex");
|
||||
const authTag = Buffer.from(authTagHex, "hex");
|
||||
|
||||
const decipher = crypto.createDecipheriv(
|
||||
"aes-256-gcm",
|
||||
Buffer.from(ENCRYPTION_KEY, "hex"),
|
||||
iv,
|
||||
);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encryptedText, undefined, "utf8");
|
||||
decrypted += decipher.final("utf8");
|
||||
|
||||
return decrypted.toString();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user