Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
942f70105a | ||
|
|
b49c152d85 | ||
|
|
d07cb39ef6 | ||
|
|
0e59668ad9 | ||
|
|
75c7d0e448 | ||
|
|
094599a424 | ||
|
|
eca06ca08e | ||
|
|
13295f997c | ||
|
|
a9048a64a1 | ||
|
|
fff0734af3 | ||
|
|
7dd6f31edb | ||
|
|
c32da046de | ||
|
|
bd704157ed | ||
|
+3 |
2eca294a67 | ||
|
|
e8efbb5e8f | ||
|
|
3f2e65ce61 | ||
|
|
218db7a5d7 | ||
|
|
69c6a74110 | ||
|
|
66f55e94fe | ||
|
|
0becc56477 | ||
|
|
6a6bbf7524 | ||
|
|
a369cf0941 | ||
|
|
cf4ed6bb2e | ||
|
|
be00006ae2 | ||
|
|
b88a41443a | ||
|
|
9843c79f57 | ||
|
|
8fcac23083 | ||
|
|
b171db153e | ||
|
|
fba07bdff0 | ||
|
|
ee7473855c | ||
|
|
53b90fed02 | ||
|
|
0ed2c53b6c | ||
|
|
2b6420b78d | ||
|
|
5e5f198aae | ||
|
|
01e4c57a55 | ||
|
|
1d7c0ae9ab | ||
|
|
25d428dc93 | ||
|
|
aedee34611 | ||
|
|
72d7a99ff2 | ||
|
|
8cb3d26e54 | ||
|
|
dee644d8cb | ||
|
|
d3388aac23 | ||
|
|
09f709627a | ||
|
|
69c730f1a0 | ||
|
|
33cfd9b4f9 | ||
|
|
3290a3381e | ||
|
|
cf40ce5093 | ||
|
|
9b53418780 | ||
|
|
e0568b0c5e | ||
|
|
d03ff3d6a9 | ||
|
|
689dc16307 | ||
|
|
32e6026f24 | ||
|
|
d9460e684d | ||
|
|
30726d6150 | ||
|
|
28adf5b129 | ||
|
|
dae07f5c93 | ||
|
|
6a18e6615c | ||
|
|
07c125bcc1 | ||
|
|
446fbe484c | ||
|
|
77733d7774 | ||
|
|
7119201dcf | ||
|
|
1354e0937f | ||
|
|
a2da9a51b3 | ||
|
|
3ce6c51009 | ||
|
|
f1564c3049 | ||
|
|
f2c764c1d7 | ||
|
|
71c7b30516 | ||
|
|
e639e866e1 | ||
|
|
304f9999dd | ||
|
|
bee927e159 | ||
|
|
288b6d4ff2 | ||
|
|
a021f99e99 | ||
|
|
2c3362de5c | ||
|
|
c6199bc931 | ||
|
|
07352dbf94 | ||
|
|
3dd795b095 | ||
|
|
c519fe1737 | ||
|
|
506d670625 | ||
|
|
4ee723d9ed | ||
|
|
d3f35d1f22 | ||
|
|
1a0a2b1b53 | ||
|
|
5bb5b105c4 | ||
|
|
7377a313e5 | ||
|
|
a1e7867a35 | ||
|
|
6081187466 | ||
|
|
adb40d1bb1 | ||
|
|
f1601bb38d | ||
|
|
466d7e32c5 | ||
|
|
819fa50f2e | ||
|
|
2d000cb826 | ||
|
|
d953606e2f | ||
|
|
3611fc3b4e | ||
|
|
fedfe24ec4 | ||
|
|
9842d842d8 | ||
|
|
6cd98d2cd8 | ||
|
|
23300f9db8 | ||
|
|
5427d3f74f | ||
|
|
a43fbc8a74 | ||
|
|
8d9c059572 | ||
|
|
37002f39bd | ||
|
|
c7de984fb1 | ||
|
|
1d3ad16710 | ||
|
|
17a358a1c2 | ||
|
|
7e76fbfcb4 | ||
|
|
aa5b5fb8a9 | ||
|
|
c084d19c2d | ||
|
|
23e765fc35 | ||
|
|
f60286f13d | ||
|
|
0f43240c35 | ||
|
|
dc4e94a68b | ||
|
|
fd98cd4cd7 | ||
|
|
a1f09a2e30 | ||
|
|
decac0cd5a | ||
|
|
17e600f65e | ||
|
|
0540828e21 | ||
|
|
4f9d7b0e60 | ||
|
|
a940fa40d9 | ||
|
|
ab2007a627 | ||
|
|
9b8868ac97 | ||
|
|
4f6e917cd7 | ||
|
|
da51c01f35 | ||
|
|
4aeea98b56 | ||
|
|
917e35a1b3 | ||
|
|
2e0a548e6f | ||
|
|
f9222c526f | ||
|
|
5cb61d6a96 | ||
|
|
463ada23ca | ||
|
|
b2d22a0720 | ||
|
|
fd1f1786da | ||
|
|
56598597df | ||
|
|
ea71c40ecb | ||
|
|
20cc20f908 | ||
|
|
6326a6d788 | ||
|
|
286f7ea29f | ||
|
|
700921f69f | ||
|
|
9e3379f05f | ||
|
|
bc178010c1 | ||
|
|
47a1cb24a3 | ||
|
|
312d97f509 | ||
|
|
7776462c9b | ||
|
|
aa8eabbc19 | ||
|
|
f4caacabdb | ||
|
|
7892ff521d | ||
|
|
45215cb4ff | ||
|
|
5404119473 | ||
|
|
7769cb9d2a | ||
|
|
0b68c410cf | ||
|
|
efa6fe7e6a | ||
|
|
16f41f809a | ||
|
|
a7d59d7bb2 | ||
|
|
291abf3205 | ||
|
|
5df5a7cd48 | ||
|
|
ac2936d316 | ||
|
|
0335dcd18d | ||
|
|
27cfbb14a1 | ||
|
|
4c5828dade | ||
|
|
2c9afcc58b | ||
|
|
d401d42377 | ||
|
|
322c767a33 | ||
|
|
53726d58bf | ||
|
|
63feb943a4 | ||
|
|
6379c7c54b | ||
|
|
69b3b81771 | ||
|
|
668d1b6933 | ||
|
|
d7069df433 | ||
|
|
83d3009331 | ||
|
|
688659c597 | ||
|
|
63ec00efe9 | ||
|
|
a11b5ffa92 | ||
|
|
72bf6a75c1 | ||
|
|
92b11374d9 | ||
|
|
828c3ff7be | ||
|
|
7b35cb0e99 | ||
|
|
8d5c445dc5 | ||
|
|
c752af2201 | ||
|
|
03f6204797 | ||
|
|
c33708e5db | ||
|
|
4c2c3d21c7 | ||
|
|
9a09fa2ad0 | ||
|
|
43e0644c64 | ||
|
|
c5be145a53 | ||
|
|
3a002f03d5 | ||
|
|
c785156220 | ||
|
|
6f8627ed86 | ||
|
|
6a7034a5ad | ||
|
|
29443bcdbd | ||
|
|
9f066a285f | ||
|
|
664bd3596e | ||
|
|
bad27d2625 | ||
|
|
4da02c1ebe | ||
|
|
65bd0bf5c1 | ||
|
|
b1ee21fc34 | ||
|
|
b894e8a620 | ||
|
|
aa39af94fb | ||
|
|
b1880ddb82 | ||
|
|
19e9da214e | ||
|
|
2e698c8ec0 | ||
|
|
46feb262ec | ||
|
|
23b830074a | ||
|
|
fa5908a749 | ||
|
|
26045d089c | ||
|
|
edfb15c010 | ||
|
|
1f76ae650f | ||
|
|
36dc608b58 | ||
|
|
bc0735729f | ||
|
|
88e553734e | ||
|
|
bd5495321e | ||
|
|
e8ebfb8e3e | ||
|
|
8c076b6b50 | ||
|
|
2cdc3cab7f | ||
|
|
729c2132af | ||
|
|
d3627bf225 | ||
|
|
05b87199ec | ||
|
|
a618d53451 | ||
|
|
9907c39861 | ||
|
|
4c8594e47a | ||
|
|
a69609229a | ||
|
|
217ae648d6 | ||
|
|
72e5b51398 | ||
|
|
0cbbf94568 | ||
|
|
61f4c3ac95 | ||
|
|
cc8f521741 | ||
|
|
540f8622e3 | ||
|
|
a7e3dec6f0 | ||
|
|
5760d51c08 | ||
|
|
59a75ce36c | ||
|
|
9a6a79f458 | ||
|
|
2598f44036 | ||
|
|
f7e7b132dd | ||
|
|
521be4e0b6 | ||
|
|
a13b5d0d32 | ||
|
|
ebe557881c | ||
|
|
5e8a9e3e80 | ||
|
|
ab115cdc19 | ||
|
|
18deee6434 | ||
|
|
977724a801 | ||
|
|
434c9be8a6 | ||
|
|
06bb3bbf43 | ||
|
|
736f4762bc | ||
|
|
4341b37ef3 | ||
|
|
95e07f2eb0 | ||
|
|
242e33c2f6 | ||
|
|
01aa0b9ecd | ||
|
|
3c0a4c863a | ||
|
|
d9954d14b1 | ||
|
|
7a2c5188cd | ||
|
|
c7f62805c4 | ||
|
|
c23d1521ab | ||
|
|
53b44454f6 | ||
|
|
8bf6044b24 | ||
|
|
a28877b9d9 | ||
|
|
902d8fc2f9 | ||
|
|
869851b974 | ||
|
|
f4118e451e | ||
|
|
a8471adcea | ||
|
|
2922387793 | ||
|
|
94124ac60c | ||
|
|
69c91c5988 | ||
|
|
dbb97ca511 | ||
|
|
3e23f0c58e | ||
|
|
e5d3659b64 | ||
|
|
7b6b8b176b | ||
|
|
98151fa706 | ||
|
|
0840ac1a9f | ||
|
|
7ad3818008 | ||
|
|
3cbe3bd1bc | ||
|
|
0781cfb194 | ||
|
|
8c40cfc97d | ||
|
|
6bc996806b | ||
|
|
b9e54d2a19 | ||
|
|
99fd7ca6ab | ||
|
|
3e083656fa | ||
|
|
ee80b102bc | ||
|
|
7dcd2ad30f | ||
|
|
43a98141a8 | ||
|
|
cf3505805d | ||
|
|
63fa75cc61 | ||
|
|
a02ef20deb | ||
|
|
2deb588a57 | ||
|
|
f04fd76f6c | ||
|
|
d022a07263 | ||
|
|
94e8f0b592 | ||
|
|
5c8e4d641a | ||
|
|
2eebca4e42 | ||
|
|
426d4b77b6 | ||
|
|
1e3f345f5b | ||
|
|
2d6e58136d | ||
|
|
cf15ee5066 | ||
|
|
816abfb59e | ||
|
|
702dd6ac27 | ||
|
|
14fbeb1788 | ||
|
|
fbf6110acf | ||
|
|
ef950ae381 | ||
|
|
79762e8eb0 | ||
|
|
c24ac641e0 | ||
|
|
9464e9bfbe | ||
|
|
2893fc44db | ||
|
|
6c0ecf75d0 | ||
|
|
56ee375a71 | ||
|
|
a54452e1c7 | ||
|
|
de4a158efb | ||
|
|
44ce6ef581 | ||
|
|
65fc850d96 | ||
|
|
af35c65d06 | ||
|
|
c0243c6703 | ||
|
|
db11e91539 | ||
|
|
9aec4081e1 | ||
|
|
e6e40403fa | ||
|
|
435b19098c | ||
|
|
753a716724 | ||
|
|
ce7ac9888e | ||
|
|
8f1a202226 | ||
|
|
3ba29710b2 | ||
|
|
dafda00e9d | ||
|
|
f2bcf2f5cc | ||
|
|
1acef7c0e8 | ||
|
|
e6b4af4004 | ||
|
|
414d950fa0 | ||
|
|
f49a20b489 | ||
|
|
3c2e963bfb | ||
|
|
8769b65157 | ||
|
|
231a8f82bc | ||
|
|
a933bf68fb | ||
|
|
21a16963e2 | ||
|
|
fe6c647c2b | ||
|
|
f1e5f35659 | ||
|
|
d39f7ba6e5 | ||
|
|
f701562396 | ||
|
|
83cbd336e2 | ||
|
|
ec33992412 | ||
|
|
4e67edaaff | ||
|
|
ac3ee18373 | ||
|
|
7f646757c5 | ||
|
|
2d82ae6e86 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
[codespell]
|
||||
skip = .git,*.pdf,*.svg,package-lock.json,*.prisma,pnpm-lock.yaml
|
||||
ignore-words-list = afterall,vertx,notIn,alue
|
||||
ignore-words-list = afterall,vertx,notIn,alue,allTime
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
FROM node:20
|
||||
|
||||
# ---------- System packages --------------------------------------------------
|
||||
# The buildpack-deps base already ships git, build-essential, python, etc.
|
||||
# Add a few extra tools handy during Langfuse development.
|
||||
RUN apt-get update --option=Acquire::Check-Valid-Until=false && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
openssl \
|
||||
wget \
|
||||
curl \
|
||||
ca-certificates \
|
||||
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"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && \
|
||||
corepack prepare pnpm@9.5.0 --activate
|
||||
|
||||
# ---------- golang-migrate ----------------------------------------------------
|
||||
# CLI used for database migrations during development.
|
||||
ENV MIGRATE_VERSION=4.18.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 -----------------------------------------------------
|
||||
# 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 && \
|
||||
chown -R ubuntu:ubuntu /pnpm
|
||||
|
||||
USER ubuntu
|
||||
WORKDIR /home/ubuntu
|
||||
ENV HOME=/home/ubuntu
|
||||
ENV TERM=xterm-256color
|
||||
|
||||
# Container starts with a bash shell ready for hacking.
|
||||
CMD ["bash"]
|
||||
@@ -1,14 +1,11 @@
|
||||
{
|
||||
"agentCanUpdateSnapshot": false,
|
||||
"install": "cp .env.dev.example .env && pnpm i",
|
||||
"build": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"start": "sudo service docker start",
|
||||
"terminals": [
|
||||
{
|
||||
"name": "dev server",
|
||||
"command": "pnpm run dx-f"
|
||||
"name": "Development Terminal",
|
||||
"command": "pnpm run dev",
|
||||
"description": "Main development terminal running the development server"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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 && \
|
||||
RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.0/migrate.linux-amd64.tar.gz | tar xvz && \
|
||||
chmod +x migrate && \
|
||||
mv migrate /usr/local/bin/migrate
|
||||
|
||||
|
||||
@@ -83,6 +83,3 @@ ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# speeds up local development by not executing init scripts on server startup
|
||||
NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT="false"
|
||||
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_WRITE_CH=true
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_READ_CH=true
|
||||
|
||||
@@ -76,6 +76,7 @@ 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_OTEL_INGESTION_QUEUE_SHARD_COUNT=4
|
||||
LANGFUSE_TRACE_UPSERT_QUEUE_SHARD_COUNT=4
|
||||
|
||||
# openssl rand -hex 32 used only here
|
||||
@@ -84,10 +85,3 @@ 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_INSERT_INTO_TRACES_TABLE="false"
|
||||
LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT="true"
|
||||
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_WRITE_CH=true
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_READ_CH=true
|
||||
|
||||
+12
-2
@@ -33,6 +33,7 @@ SALT="salt"
|
||||
# Email
|
||||
EMAIL_FROM_ADDRESS="" # Defines the email address to use as the from address.
|
||||
SMTP_CONNECTION_URL="" # Defines the connection url for smtp server.
|
||||
CLOUD_CRM_EMAIL="" # Optional BCC address for usage threshold emails (e.g., for CRM integration like HubSpot)
|
||||
|
||||
# S3 Batch Exports
|
||||
LANGFUSE_S3_BATCH_EXPORT_ENABLED=true
|
||||
@@ -89,5 +90,14 @@ SLACK_CLIENT_ID=your_slack_client_id
|
||||
SLACK_CLIENT_SECRET=your_slack_client_secret
|
||||
SLACK_STATE_SECRET=your_slack_state_secret
|
||||
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_WRITE_CH=true
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_READ_CH=true
|
||||
# Langfuse AI instance for tracing, prompts
|
||||
LANGFUSE_AI_FEATURES_PUBLIC_KEY="pk-lf-1234567890"
|
||||
LANGFUSE_AI_FEATURES_SECRET_KEY="sk-lf-1234567890"
|
||||
LANGFUSE_AI_FEATURES_HOST="http://localhost:3000"
|
||||
LANGFUSE_AI_FEATURES_PROJECT_ID=7a88fb47-b4e2-43b8-a06c-a5ce950dc53a
|
||||
|
||||
# Langfuse AI Bedrock credentials
|
||||
AWS_ACCESS_KEY_ID="A123456789"
|
||||
AWS_SECRET_ACCESS_KEY="SAK123456789"
|
||||
LANGFUSE_AWS_BEDROCK_REGION="eu-west-1"
|
||||
LANGFUSE_AWS_BEDROCK_MODEL="eu.anthropic.claude-3-haiku-20240307-v1:0"
|
||||
|
||||
@@ -270,6 +270,14 @@ LANGFUSE_ENABLE_BLOB_STORAGE_FILE_LOG=true
|
||||
# Rate limiting
|
||||
# LANGFUSE_RATE_LIMITS_ENABLED=
|
||||
|
||||
# Free tier usage thresholds (Cloud deployments only)
|
||||
# Enable the queue consumer that monitors free tier usage (default: true, but requires cloud region)
|
||||
# QUEUE_CONSUMER_FREE_TIER_USAGE_THRESHOLD_QUEUE_IS_ENABLED=true
|
||||
# Enable enforcement: send emails and block orgs that exceed free tier limits (default: false)
|
||||
# LANGFUSE_FREE_TIER_USAGE_THRESHOLD_ENFORCEMENT_ENABLED=false
|
||||
# Optional BCC address for usage threshold emails (e.g., for CRM integration like HubSpot)
|
||||
# CLOUD_CRM_EMAIL=
|
||||
|
||||
# Stripe
|
||||
# STRIPE_SECRET_KEY=
|
||||
# STRIPE_WEBHOOK_SIGNING_SECRET=
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe your question
|
||||
description: Please describe the question you have as clear and concise as possible.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: hosting
|
||||
attributes:
|
||||
label: Are you using Langfuse Cloud or self-host Langfuse?
|
||||
options:
|
||||
- Langfuse Cloud
|
||||
- Self-hosted Langfuse
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: I checked if there is already an [issue](https://github.com/langfuse/langfuse/issues) or [discussion](https://github.com/orgs/langfuse/discussions) for my question and asked the [Langfuse AI](https://langfuse.com/docs/ask-ai) for help.
|
||||
validations:
|
||||
required: true
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
service:
|
||||
description: 'Service to be deployed'
|
||||
description: "Service to be deployed"
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
- worker
|
||||
required: true
|
||||
environment:
|
||||
description: 'Environment to deploy to'
|
||||
description: "Environment to deploy to"
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
- name: install dependencies
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
- name: install dependencies
|
||||
@@ -82,12 +82,12 @@ jobs:
|
||||
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
|
||||
@@ -142,13 +142,13 @@ jobs:
|
||||
name: tests-web-sync (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }})
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
node-version: [24]
|
||||
postgres-version: [12, 15]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.0/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- uses: pnpm/action-setup@v3
|
||||
@@ -189,6 +189,8 @@ jobs:
|
||||
pnpm --filter=shared ch:up
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
- name: Start Langfuse
|
||||
run: (pnpm run start&)
|
||||
env:
|
||||
@@ -216,14 +218,14 @@ jobs:
|
||||
name: tests-web-async (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.deploy-mode }})
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
node-version: [24]
|
||||
postgres-version: [12, 15]
|
||||
deploy-mode: ["", "-azure", "-redis-cluster"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.0/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- uses: pnpm/action-setup@v3
|
||||
@@ -266,6 +268,8 @@ jobs:
|
||||
pnpm --filter=shared ch:up
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
- name: Start Langfuse
|
||||
run: (pnpm run start&)
|
||||
env:
|
||||
@@ -291,7 +295,7 @@ jobs:
|
||||
name: tests-worker (node${{ matrix.node-version }}, pg${{ matrix.postgres-version }}, mode${{ matrix.deploy-mode }})
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
node-version: [24]
|
||||
postgres-version: [12, 15]
|
||||
deploy-mode: ["", "-azure", "-redis-cluster"]
|
||||
steps:
|
||||
@@ -316,7 +320,7 @@ jobs:
|
||||
pnpm install
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.0/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Load default env
|
||||
@@ -345,7 +349,80 @@ jobs:
|
||||
- name: Build
|
||||
run: pnpm --filter=worker... run build
|
||||
- name: run tests
|
||||
run: pnpm --filter=worker run test
|
||||
run: pnpm --filter=worker run test:exclude-llm-connections
|
||||
|
||||
test-worker-llm-connections:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- pre-job
|
||||
if: needs.pre-job.outputs.should_skip != 'true'
|
||||
name: test-worker-llm-connections (node24, pg15)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.5.0
|
||||
- name: Login to Docker Hub
|
||||
if: github.repository == 'langfuse/langfuse' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME_READ }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN_READ }}
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
- name: install dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.0/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Load default env
|
||||
run: |
|
||||
cp .env.dev.example .env
|
||||
cp .env.dev.example web/.env
|
||||
cp .env.dev.example worker/.env
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
docker compose ps
|
||||
env:
|
||||
POSTGRES_VERSION: 15
|
||||
- name: Ensure no unhealthy status
|
||||
run: |
|
||||
if docker compose ps | grep "(unhealthy)"; then
|
||||
echo "One or more services are unhealthy"
|
||||
exit 1
|
||||
else
|
||||
echo "All services are healthy"
|
||||
fi
|
||||
- name: Seed DB
|
||||
run: |
|
||||
pnpm run db:migrate
|
||||
pnpm run db:seed
|
||||
pnpm run --filter=shared ch:up
|
||||
- name: Build
|
||||
run: pnpm --filter=worker... run build
|
||||
- name: run llm connection tests
|
||||
run: pnpm --filter=worker run test:llm-connections-only
|
||||
env:
|
||||
LANGFUSE_LLM_CONNECTION_OPENAI_KEY: ${{ secrets.LANGFUSE_LLM_CONNECTION_OPENAI_KEY }}
|
||||
LANGFUSE_LLM_CONNECTION_ANTHROPIC_KEY: ${{ secrets.LANGFUSE_LLM_CONNECTION_ANTHROPIC_KEY }}
|
||||
LANGFUSE_LLM_CONNECTION_AZURE_KEY: ${{ secrets.LANGFUSE_LLM_CONNECTION_AZURE_KEY }}
|
||||
LANGFUSE_LLM_CONNECTION_AZURE_BASE_URL: ${{ secrets.LANGFUSE_LLM_CONNECTION_AZURE_BASE_URL }}
|
||||
LANGFUSE_LLM_CONNECTION_AZURE_MODEL: ${{ secrets.LANGFUSE_LLM_CONNECTION_AZURE_MODEL }}
|
||||
LANGFUSE_LLM_CONNECTION_BEDROCK_ACCESS_KEY_ID: ${{ secrets.LANGFUSE_LLM_CONNECTION_BEDROCK_ACCESS_KEY_ID }}
|
||||
LANGFUSE_LLM_CONNECTION_BEDROCK_SECRET_ACCESS_KEY: ${{ secrets.LANGFUSE_LLM_CONNECTION_BEDROCK_SECRET_ACCESS_KEY }}
|
||||
LANGFUSE_LLM_CONNECTION_BEDROCK_REGION: ${{ secrets.LANGFUSE_LLM_CONNECTION_BEDROCK_REGION }}
|
||||
LANGFUSE_LLM_CONNECTION_VERTEXAI_KEY: ${{ secrets.LANGFUSE_LLM_CONNECTION_VERTEXAI_KEY }}
|
||||
LANGFUSE_LLM_CONNECTION_GOOGLEAISTUDIO_KEY: ${{ secrets.LANGFUSE_LLM_CONNECTION_GOOGLEAISTUDIO_KEY }}
|
||||
|
||||
e2e-tests:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -359,7 +436,7 @@ jobs:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
- name: Login to Docker Hub
|
||||
@@ -377,7 +454,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.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.0/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Run + migrate
|
||||
@@ -400,6 +477,8 @@ jobs:
|
||||
pnpm run db:seed
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
- name: Install playwright
|
||||
run: pnpm --filter=web exec playwright install --with-deps
|
||||
- name: Run e2e tests
|
||||
@@ -423,7 +502,7 @@ jobs:
|
||||
version: 9.5.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
- name: install dependencies
|
||||
@@ -431,7 +510,7 @@ jobs:
|
||||
pnpm install
|
||||
- name: Install golang-migrate for Clickhouse migrations
|
||||
run: |
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.3/migrate.linux-amd64.tar.gz | tar xvz
|
||||
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.0/migrate.linux-amd64.tar.gz | tar xvz
|
||||
sudo mv migrate /usr/bin/migrate
|
||||
which migrate
|
||||
- name: Load default env
|
||||
@@ -457,6 +536,8 @@ jobs:
|
||||
pnpm run db:seed:examples
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
- name: Run server
|
||||
run: (pnpm run start&)
|
||||
- name: Check worker health
|
||||
@@ -477,6 +558,7 @@ jobs:
|
||||
prettier-check,
|
||||
tests-web-sync,
|
||||
tests-worker,
|
||||
test-worker-llm-connections,
|
||||
e2e-tests,
|
||||
test-docker-build,
|
||||
e2e-server-tests,
|
||||
@@ -517,7 +599,7 @@ jobs:
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
name: SDK API Spec Generation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'fern/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: sdk-api-spec-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
generate-sdk-api-specs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Generate SDK specs
|
||||
env:
|
||||
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
|
||||
run: npx fern-api generate --api server --force
|
||||
|
||||
- name: Update Python SDK
|
||||
env:
|
||||
GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
run: |
|
||||
# Clone langfuse-python repository
|
||||
git clone https://github.com/langfuse/langfuse-python.git ../langfuse-python
|
||||
cd ../langfuse-python
|
||||
|
||||
git config user.name "langfuse-bot"
|
||||
git config user.email "langfuse-bot@langfuse.com"
|
||||
echo "$GH_ACCESS_TOKEN" | gh auth login --with-token
|
||||
gh auth setup-git
|
||||
|
||||
# Delete existing API contents
|
||||
rm -rf langfuse/api/*
|
||||
|
||||
# Copy generated Python SDK files
|
||||
cp -r ../langfuse/generated/python/* langfuse/api/
|
||||
|
||||
# Remove unnecessary integration tests created by Fern
|
||||
# (could not find the right option to prevent this in generation step)
|
||||
rm -rf langfuse/api/tests
|
||||
|
||||
# Install poetry and format
|
||||
pip install poetry
|
||||
poetry install --all-extras
|
||||
poetry run ruff format langfuse/api
|
||||
|
||||
# Check for changes and show diff for debugging
|
||||
if git diff --quiet; then
|
||||
echo "No changes detected in Python SDK"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Close existing api-spec-bot PRs
|
||||
gh pr list --author langfuse-bot --state open --json number --jq '.[].number' | xargs -I {} gh pr close {}
|
||||
|
||||
# Get the GitHub username of the original commit author
|
||||
cd ../langfuse
|
||||
ORIGINAL_AUTHOR=$(gh api repos/langfuse/langfuse/commits/${GITHUB_SHA} --jq '.author.login')
|
||||
cd ../langfuse-python
|
||||
|
||||
# Create new branch and push changes
|
||||
BRANCH_NAME="api-spec-bot-${GITHUB_SHA::7}"
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
git add .
|
||||
git commit -m "feat(api): update API spec from langfuse/langfuse ${GITHUB_SHA::7}"
|
||||
git push origin "$BRANCH_NAME"
|
||||
|
||||
# Create PR with original author as reviewer
|
||||
gh pr create --title "feat(api): update API spec from langfuse/langfuse ${GITHUB_SHA::7}" --body "" --reviewer "$ORIGINAL_AUTHOR"
|
||||
|
||||
- name: Update TypeScript SDK
|
||||
env:
|
||||
GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
run: |
|
||||
# Clone langfuse-js repository
|
||||
git clone https://github.com/langfuse/langfuse-js.git ../langfuse-js
|
||||
cd ../langfuse-js
|
||||
|
||||
# Configure git
|
||||
git config user.name "langfuse-bot"
|
||||
git config user.email "langfuse-bot@langfuse.com"
|
||||
echo "$GH_ACCESS_TOKEN" | gh auth login --with-token
|
||||
gh auth setup-git
|
||||
|
||||
# Delete existing API contents
|
||||
rm -rf packages/core/src/api/*
|
||||
|
||||
# Copy generated TypeScript SDK files
|
||||
cp -r ../langfuse/generated/typescript/* packages/core/src/api/
|
||||
|
||||
# Patch compilation error inducing output
|
||||
# Without this patch, the JS SDK will not build due to
|
||||
# "Error: packages/core build: src/api/core/auth/index.ts(1,10): error TS1205: Re-exporting a type when 'isolatedModules' is enabled requires using 'export type'."
|
||||
if [ -f "packages/core/src/api/core/auth/index.ts" ]; then
|
||||
if grep -q '^export { AuthProvider } from "./AuthProvider.js";$' packages/core/src/api/core/auth/index.ts; then
|
||||
sed -i '1s/^export { AuthProvider }/export { type AuthProvider }/' packages/core/src/api/core/auth/index.ts
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install dependencies and format
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm format
|
||||
|
||||
# Check for changes and show diff for debugging
|
||||
if git diff --quiet; then
|
||||
echo "No changes detected in TypeScript SDK"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Close existing api-spec-bot PRs
|
||||
gh pr list --author langfuse-bot --state open --json number --jq '.[].number' | xargs -I {} gh pr close {}
|
||||
|
||||
# Get the GitHub username of the original commit author
|
||||
cd ../langfuse
|
||||
ORIGINAL_AUTHOR=$(gh api repos/langfuse/langfuse/commits/${GITHUB_SHA} --jq '.author.login')
|
||||
cd ../langfuse-js
|
||||
|
||||
# Create new branch and push changes
|
||||
BRANCH_NAME="api-spec-bot-${GITHUB_SHA::7}"
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
git add .
|
||||
git commit -m "feat(api): update API spec from langfuse/langfuse ${GITHUB_SHA::7}" --no-verify
|
||||
git push origin "$BRANCH_NAME"
|
||||
|
||||
# Create PR with original author as reviewer
|
||||
gh pr create --title "feat(api): update API spec from langfuse/langfuse ${GITHUB_SHA::7}" --body "" --reviewer "$ORIGINAL_AUTHOR"
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: "Validate PR Title"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
- reopened
|
||||
|
||||
jobs:
|
||||
validate-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
statuses: write
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Validate PR title follows conventional commits
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# Configure the types of commits allowed
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
perf
|
||||
test
|
||||
build
|
||||
ci
|
||||
chore
|
||||
revert
|
||||
# Allow scopes (optional)
|
||||
requireScope: false
|
||||
# Allow any scope (not just the ones listed above)
|
||||
validateSingleCommit: false
|
||||
ignoreLabels: |
|
||||
bot
|
||||
ignore-semantic-pull-request
|
||||
@@ -1 +1,3 @@
|
||||
public-hoist-pattern[]=*prisma*
|
||||
public-hoist-pattern[]=*prisma*
|
||||
# for turbopack, ongoing issue see: https://github.com/vercel/next.js/issues/68805
|
||||
public-hoist-pattern[]=@aws-sdk/client-s3
|
||||
|
||||
@@ -85,6 +85,8 @@ Depending on the file location (sync, async)
|
||||
pnpm test-sync --testPathPattern="$FILE_LOCATION_PATTERN" --testNamePattern="$TEST_NAME_PATTERN"
|
||||
# For tests in the async folder:
|
||||
pnpm test -- --testPathPattern="$FILE_LOCATION_PATTERN" --testNamePattern="$TEST_NAME_PATTERN"
|
||||
# For client tests:
|
||||
pnpm test-client --testPathPattern="buildStepData" --testNamePattern="buildStepData"
|
||||
```
|
||||
|
||||
### Testing in the Worker Package
|
||||
@@ -166,7 +168,7 @@ pnpm run nuke # Remove all node_modules, build files, wipe database
|
||||
|
||||
## Environment Setup
|
||||
|
||||
- **Node.js**: Version 20 (specified in `.nvmrc`)
|
||||
- **Node.js**: Version 24 (specified in `.nvmrc`)
|
||||
- **Package Manager**: pnpm v9.5.0
|
||||
- **Database Dependencies**: Docker for local PostgreSQL, ClickHouse, Redis, MinIO
|
||||
- **Environment**: Copy `.env.dev.example` to `.env`
|
||||
@@ -195,4 +197,4 @@ To get a project, use the `get_project` capability with the full project name as
|
||||
- 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
|
||||
- Before trying to build the package, try running the linter once first
|
||||
|
||||
+9
-7
@@ -108,15 +108,19 @@ We built a monorepo using [pnpm](https://pnpm.io/motivation) and [turbo](https:/
|
||||
|
||||
Requirements
|
||||
|
||||
- Node.js 20 as specified in the [.nvmrc](.nvmrc)
|
||||
- Node.js 24 as specified in the [.nvmrc](.nvmrc)
|
||||
- Pnpm v.9.5.0
|
||||
- Docker to run the database locally
|
||||
- Clickhouse client
|
||||
|
||||
**Note:** You can also simply run Langfuse in a **GitHub Codespace** via the provided devcontainer. To do this, click on the green "Code" button in the top right corner of the repository and select "Open with Codespaces".
|
||||
|
||||
**Steps**
|
||||
|
||||
1. Install [golang-migrate](https://github.com/golang-migrate/migrate/tree/master/cmd/migrate#migrate-cli) as CLI
|
||||
1. Install development dependencies:
|
||||
- [golang-migrate](https://github.com/golang-migrate/migrate/tree/master/cmd/migrate#migrate-cli) as CLI
|
||||
- [clickhouse binary](https://clickhouse.com/docs/install) on macOS with brew: `brew install --cask clickhouse`
|
||||
|
||||
2. Fork the repository and clone it locally
|
||||
|
||||
```bash
|
||||
@@ -137,7 +141,7 @@ Requirements
|
||||
cp .env.dev.example .env
|
||||
```
|
||||
|
||||
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.
|
||||
5. 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, docker containers, etc...)
|
||||
@@ -146,13 +150,11 @@ Requirements
|
||||
|
||||
You will be asked whether you want to reset Postgres and ClickHouse. Confirm both with 'Y' and press enter.
|
||||
|
||||
5. Open the web app in your browser to start using Langfuse:
|
||||
|
||||
6. Open the web app in your browser to start using Langfuse:
|
||||
- [Sign up page, http://localhost:3000](http://localhost:3000)
|
||||
- [Demo project, http://localhost:3000/project/7a88fb47-b4e2-43b8-a06c-a5ce950dc53a](http://localhost:3000/project/7a88fb47-b4e2-43b8-a06c-a5ce950dc53a)
|
||||
|
||||
6. Log in as a test user:
|
||||
|
||||
7. Log in as a test user:
|
||||
- Username: `demo@langfuse.com`
|
||||
- Password: `password`
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||
<img width="4856" height="1000" alt="github-banner" src="https://github.com/user-attachments/assets/6f435ef3-1194-4e26-87af-aa13826bbb5f" />
|
||||
|
||||
<div align="center">
|
||||
<div>
|
||||
@@ -76,7 +76,7 @@ Langfuse is an **open source LLM engineering** platform. It helps teams collabor
|
||||
|
||||
## ✨ Core Features
|
||||
|
||||

|
||||
<img width="4856" height="1944" alt="Langfuse Overview" src="https://github.com/user-attachments/assets/5dac68ef-d546-49fb-b06f-cfafc19282e3" />
|
||||
|
||||
- [LLM Application Observability](https://langfuse.com/docs/tracing): Instrument your app and start ingesting traces to Langfuse, thereby tracking LLM calls and other relevant logic in your app such as retrieval, embedding, or agent actions. Inspect and debug complex logs and user sessions. Try the interactive [demo](https://langfuse.com/docs/demo) to see this in action.
|
||||
|
||||
@@ -92,7 +92,7 @@ Langfuse is an **open source LLM engineering** platform. It helps teams collabor
|
||||
|
||||
## 📦 Deploy Langfuse
|
||||
|
||||

|
||||
<img width="4856" height="1322" alt="Langfuse Deployment Options" src="https://github.com/user-attachments/assets/98f020c7-7a20-4264-a201-65c41a52a5d5" />
|
||||
|
||||
### Langfuse Cloud
|
||||
|
||||
@@ -127,7 +127,7 @@ See [self-hosting documentation](https://langfuse.com/self-hosting) to learn mor
|
||||
|
||||
## 🔌 Integrations
|
||||
|
||||

|
||||
<img width="4856" height="1322" alt="github-integrations" src="https://github.com/user-attachments/assets/e41ea0fb-742d-41ce-bf94-1d4fb95750cd" />
|
||||
|
||||
### Main Integrations:
|
||||
|
||||
@@ -215,7 +215,7 @@ main()
|
||||
|
||||
See your language model calls and other application logic in Langfuse.
|
||||
|
||||

|
||||
<img width="1787" height="674" alt="Example trace in Langfuse" src="https://github.com/user-attachments/assets/f796eb78-dfb5-4570-b236-bdb4b67d4d55" />
|
||||
|
||||
_[Public example trace in Langfuse](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/2cec01e3-3dc2-472f-afcf-3b968cf0c1f4?timestamp=2025-02-10T14%3A27%3A30.275Z&observation=cb5ff844-07ef-41e6-b8e2-6c64344bc13b)_
|
||||
|
||||
@@ -268,52 +268,105 @@ This repository is MIT licensed, except for the `ee` folders. See [LICENSE](LICE
|
||||
|
||||
Top open-source Python projects that use Langfuse, ranked by stars ([Source](https://github.com/langfuse/langfuse-docs/blob/main/components-mdx/dependents)):
|
||||
|
||||
| Repository | Stars |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----: |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/127165244?s=40&v=4" width="20" height="20" alt=""> [langgenius](https://github.com/langgenius) / [dify](https://github.com/langgenius/dify) | 54865 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/158137808?s=40&v=4" width="20" height="20" alt=""> [open-webui](https://github.com/open-webui) / [open-webui](https://github.com/open-webui/open-webui) | 51531 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/131470832?s=40&v=4" width="20" height="20" alt=""> [lobehub](https://github.com/lobehub) / [lobe-chat](https://github.com/lobehub/lobe-chat) | 49003 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/85702467?s=40&v=4" width="20" height="20" alt=""> [langflow-ai](https://github.com/langflow-ai) / [langflow](https://github.com/langflow-ai/langflow) | 39093 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/130722866?s=40&v=4" width="20" height="20" alt=""> [run-llama](https://github.com/run-llama) / [llama_index](https://github.com/run-llama/llama_index) | 37368 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/139558948?s=40&v=4" width="20" height="20" alt=""> [chatchat-space](https://github.com/chatchat-space) / [Langchain-Chatchat](https://github.com/chatchat-space/Langchain-Chatchat) | 32486 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/128289781?s=40&v=4" width="20" height="20" alt=""> [FlowiseAI](https://github.com/FlowiseAI) / [Flowise](https://github.com/FlowiseAI/Flowise) | 32448 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/31035808?s=40&v=4" width="20" height="20" alt=""> [mindsdb](https://github.com/mindsdb) / [mindsdb](https://github.com/mindsdb/mindsdb) | 26931 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/119600397?s=40&v=4" width="20" height="20" alt=""> [twentyhq](https://github.com/twentyhq) / [twenty](https://github.com/twentyhq/twenty) | 24195 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/60330232?s=40&v=4" width="20" height="20" alt=""> [PostHog](https://github.com/PostHog) / [posthog](https://github.com/PostHog/posthog) | 22618 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/121462774?s=40&v=4" width="20" height="20" alt=""> [BerriAI](https://github.com/BerriAI) / [litellm](https://github.com/BerriAI/litellm) | 15151 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/179202840?s=40&v=4" width="20" height="20" alt=""> [mediar-ai](https://github.com/mediar-ai) / [screenpipe](https://github.com/mediar-ai/screenpipe) | 11037 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/105877416?s=40&v=4" width="20" height="20" alt=""> [formbricks](https://github.com/formbricks) / [formbricks](https://github.com/formbricks/formbricks) | 9386 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/76263028?s=40&v=4" width="20" height="20" alt=""> [anthropics](https://github.com/anthropics) / [courses](https://github.com/anthropics/courses) | 8385 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/78410652?s=40&v=4" width="20" height="20" alt=""> [GreyDGL](https://github.com/GreyDGL) / [PentestGPT](https://github.com/GreyDGL/PentestGPT) | 7374 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/152537519?s=40&v=4" width="20" height="20" alt=""> [superagent-ai](https://github.com/superagent-ai) / [superagent](https://github.com/superagent-ai/superagent) | 5391 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/137907881?s=40&v=4" width="20" height="20" alt=""> [promptfoo](https://github.com/promptfoo) / [promptfoo](https://github.com/promptfoo/promptfoo) | 4976 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/157326433?s=40&v=4" width="20" height="20" alt=""> [onlook-dev](https://github.com/onlook-dev) / [onlook](https://github.com/onlook-dev/onlook) | 4141 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/7250217?s=40&v=4" width="20" height="20" alt=""> [Canner](https://github.com/Canner) / [WrenAI](https://github.com/Canner/WrenAI) | 2526 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/11855343?s=40&v=4" width="20" height="20" alt=""> [pingcap](https://github.com/pingcap) / [autoflow](https://github.com/pingcap/autoflow) | 2061 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/85268109?s=40&v=4" width="20" height="20" alt=""> [MLSysOps](https://github.com/MLSysOps) / [MLE-agent](https://github.com/MLSysOps/MLE-agent) | 1161 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/158137808?s=40&v=4" width="20" height="20" alt=""> [open-webui](https://github.com/open-webui) / [pipelines](https://github.com/open-webui/pipelines) | 1100 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/18422723?s=40&v=4" width="20" height="20" alt=""> [alishobeiri](https://github.com/alishobeiri) / [thread](https://github.com/alishobeiri/thread) | 1074 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/125468716?s=40&v=4" width="20" height="20" alt=""> [topoteretes](https://github.com/topoteretes) / [cognee](https://github.com/topoteretes/cognee) | 971 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/188657705?s=40&v=4" width="20" height="20" alt=""> [bRAGAI](https://github.com/bRAGAI) / [bRAG-langchain](https://github.com/bRAGAI/bRAG-langchain) | 823 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/169500408?s=40&v=4" width="20" height="20" alt=""> [opslane](https://github.com/opslane) / [opslane](https://github.com/opslane/opslane) | 677 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/151867818?s=40&v=4" width="20" height="20" alt=""> [dynamiq-ai](https://github.com/dynamiq-ai) / [dynamiq](https://github.com/dynamiq-ai/dynamiq) | 639 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/48585267?s=40&v=4" width="20" height="20" alt=""> [theopenconversationkit](https://github.com/theopenconversationkit) / [tock](https://github.com/theopenconversationkit/tock) | 514 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/20493493?s=40&v=4" width="20" height="20" alt=""> [andysingal](https://github.com/andysingal) / [llm-course](https://github.com/andysingal/llm-course) | 394 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/132396805?s=40&v=4" width="20" height="20" alt=""> [phospho-app](https://github.com/phospho-app) / [phospho](https://github.com/phospho-app/phospho) | 384 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/178644984?s=40&v=4" width="20" height="20" alt=""> [sentient-engineering](https://github.com/sentient-engineering) / [agent-q](https://github.com/sentient-engineering/agent-q) | 370 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/168552753?s=40&v=4" width="20" height="20" alt=""> [sql-agi](https://github.com/sql-agi) / [DB-GPT](https://github.com/sql-agi/DB-GPT) | 324 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/60330232?s=40&v=4" width="20" height="20" alt=""> [PostHog](https://github.com/PostHog) / [posthog-foss](https://github.com/PostHog/posthog-foss) | 305 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/154247157?s=40&v=4" width="20" height="20" alt=""> [vespperhq](https://github.com/vespperhq) / [vespper](https://github.com/vespperhq/vespper) | 304 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/185116535?s=40&v=4" width="20" height="20" alt=""> [block](https://github.com/block) / [goose](https://github.com/block/goose) | 295 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/609489?s=40&v=4" width="20" height="20" alt=""> [aorwall](https://github.com/aorwall) / [moatless-tools](https://github.com/aorwall/moatless-tools) | 291 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/2357342?s=40&v=4" width="20" height="20" alt=""> [dmayboroda](https://github.com/dmayboroda) / [minima](https://github.com/dmayboroda/minima) | 221 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/66303003?s=40&v=4" width="20" height="20" alt=""> [RobotecAI](https://github.com/RobotecAI) / [rai](https://github.com/RobotecAI/rai) | 172 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/148684274?s=40&v=4" width="20" height="20" alt=""> [i-am-alice](https://github.com/i-am-alice) / [3rd-devs](https://github.com/i-am-alice/3rd-devs) | 148 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/171735272?s=40&v=4" width="20" height="20" alt=""> [8090-inc](https://github.com/8090-inc) / [xrx-sample-apps](https://github.com/8090-inc/xrx-sample-apps) | 138 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/104478511?s=40&v=4" width="20" height="20" alt=""> [babelcloud](https://github.com/babelcloud) / [LLM-RGB](https://github.com/babelcloud/LLM-RGB) | 135 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/15125613?s=40&v=4" width="20" height="20" alt=""> [souzatharsis](https://github.com/souzatharsis) / [tamingLLMs](https://github.com/souzatharsis/tamingLLMs) | 129 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/169401942?s=40&v=4" width="20" height="20" alt=""> [LibreChat-AI](https://github.com/LibreChat-AI) / [librechat.ai](https://github.com/LibreChat-AI/librechat.ai) | 128 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/51827949?s=40&v=4" width="20" height="20" alt=""> [deepset-ai](https://github.com/deepset-ai) / [haystack-core-integrations](https://github.com/deepset-ai/haystack-core-integrations) | 126 |
|
||||
| Repository | Stars |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/85702467?s=40&v=4" width="20" height="20" alt=""> [langflow-ai](https://github.com/langflow-ai) / [langflow](https://github.com/langflow-ai/langflow) | 116251 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/158137808?s=40&v=4" width="20" height="20" alt=""> [open-webui](https://github.com/open-webui) / [open-webui](https://github.com/open-webui/open-webui) | 109642 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/23818?s=40&v=4" width="20" height="20" alt=""> [abi](https://github.com/abi) / [screenshot-to-code](https://github.com/abi/screenshot-to-code) | 70877 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/131470832?s=40&v=4" width="20" height="20" alt=""> [lobehub](https://github.com/lobehub) / [lobe-chat](https://github.com/lobehub/lobe-chat) | 65454 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/69962740?s=40&v=4" width="20" height="20" alt=""> [infiniflow](https://github.com/infiniflow) / [ragflow](https://github.com/infiniflow/ragflow) | 64118 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/135057108?s=40&v=4" width="20" height="20" alt=""> [firecrawl](https://github.com/firecrawl) / [firecrawl](https://github.com/firecrawl/firecrawl) | 56713 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/130722866?s=40&v=4" width="20" height="20" alt=""> [run-llama](https://github.com/run-llama) / [llama_index](https://github.com/run-llama/llama_index) | 44203 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/128289781?s=40&v=4" width="20" height="20" alt=""> [FlowiseAI](https://github.com/FlowiseAI) / [Flowise](https://github.com/FlowiseAI/Flowise) | 43547 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/159330290?s=40&v=4" width="20" height="20" alt=""> [QuivrHQ](https://github.com/QuivrHQ) / [quivr](https://github.com/QuivrHQ/quivr) | 38415 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/6154722?s=40&v=4" width="20" height="20" alt=""> [microsoft](https://github.com/microsoft) / [ai-agents-for-beginners](https://github.com/microsoft/ai-agents-for-beginners) | 38012 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/139558948?s=40&v=4" width="20" height="20" alt=""> [chatchat-space](https://github.com/chatchat-space) / [Langchain-Chatchat](https://github.com/chatchat-space/Langchain-Chatchat) | 36071 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/31035808?s=40&v=4" width="20" height="20" alt=""> [mindsdb](https://github.com/mindsdb) / [mindsdb](https://github.com/mindsdb/mindsdb) | 35669 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/121462774?s=40&v=4" width="20" height="20" alt=""> [BerriAI](https://github.com/BerriAI) / [litellm](https://github.com/BerriAI/litellm) | 28726 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/157326433?s=40&v=4" width="20" height="20" alt=""> [onlook-dev](https://github.com/onlook-dev) / [onlook](https://github.com/onlook-dev/onlook) | 22447 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/487568?s=40&v=4" width="20" height="20" alt=""> [NixOS](https://github.com/NixOS) / [nixpkgs](https://github.com/NixOS/nixpkgs) | 21748 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/170767358?s=40&v=4" width="20" height="20" alt=""> [kortix-ai](https://github.com/kortix-ai) / [suna](https://github.com/kortix-ai/suna) | 17976 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/76263028?s=40&v=4" width="20" height="20" alt=""> [anthropics](https://github.com/anthropics) / [courses](https://github.com/anthropics/courses) | 17057 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/149120496?s=40&v=4" width="20" height="20" alt=""> [mastra-ai](https://github.com/mastra-ai) / [mastra](https://github.com/mastra-ai/mastra) | 16484 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/134601687?s=40&v=4" width="20" height="20" alt=""> [langfuse](https://github.com/langfuse) / [langfuse](https://github.com/langfuse/langfuse) | 16054 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/7250217?s=40&v=4" width="20" height="20" alt=""> [Canner](https://github.com/Canner) / [WrenAI](https://github.com/Canner/WrenAI) | 11868 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/137907881?s=40&v=4" width="20" height="20" alt=""> [promptfoo](https://github.com/promptfoo) / [promptfoo](https://github.com/promptfoo/promptfoo) | 8350 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/193350244?s=40&v=4" width="20" height="20" alt=""> [The-Pocket](https://github.com/The-Pocket) / [PocketFlow](https://github.com/The-Pocket/PocketFlow) | 8313 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/139012218?s=40&v=4" width="20" height="20" alt=""> [OpenPipe](https://github.com/OpenPipe) / [ART](https://github.com/OpenPipe/ART) | 7093 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/125468716?s=40&v=4" width="20" height="20" alt=""> [topoteretes](https://github.com/topoteretes) / [cognee](https://github.com/topoteretes/cognee) | 7011 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/3299148?s=40&v=4" width="20" height="20" alt=""> [awslabs](https://github.com/awslabs) / [agent-squad](https://github.com/awslabs/agent-squad) | 6785 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/162546372?s=40&v=4" width="20" height="20" alt=""> [BasedHardware](https://github.com/BasedHardware) / [omi](https://github.com/BasedHardware/omi) | 6231 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/118301880?s=40&v=4" width="20" height="20" alt=""> [hatchet-dev](https://github.com/hatchet-dev) / [hatchet](https://github.com/hatchet-dev/hatchet) | 6019 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/88676955?s=40&v=4" width="20" height="20" alt=""> [zenml-io](https://github.com/zenml-io) / [zenml](https://github.com/zenml-io/zenml) | 4873 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/132635806?s=40&v=4" width="20" height="20" alt=""> [refly-ai](https://github.com/refly-ai) / [refly](https://github.com/refly-ai/refly) | 4654 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/47287758?s=40&v=4" width="20" height="20" alt=""> [coleam00](https://github.com/coleam00) / [ottomator-agents](https://github.com/coleam00/ottomator-agents) | 4165 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/8251002?s=40&v=4" width="20" height="20" alt=""> [JoshuaC215](https://github.com/JoshuaC215) / [agent-service-toolkit](https://github.com/JoshuaC215/agent-service-toolkit) | 3557 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/185852128?s=40&v=4" width="20" height="20" alt=""> [colanode](https://github.com/colanode) / [colanode](https://github.com/colanode/colanode) | 3517 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/201282378?s=40&v=4" width="20" height="20" alt=""> [VoltAgent](https://github.com/VoltAgent) / [voltagent](https://github.com/VoltAgent/voltagent) | 3210 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/188657705?s=40&v=4" width="20" height="20" alt=""> [bragai](https://github.com/bragai) / [bRAG-langchain](https://github.com/bragai/bRAG-langchain) | 3010 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/11855343?s=40&v=4" width="20" height="20" alt=""> [pingcap](https://github.com/pingcap) / [autoflow](https://github.com/pingcap/autoflow) | 2651 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/135396723?s=40&v=4" width="20" height="20" alt=""> [sourcebot-dev](https://github.com/sourcebot-dev) / [sourcebot](https://github.com/sourcebot-dev/sourcebot) | 2570 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/158137808?s=40&v=4" width="20" height="20" alt=""> [open-webui](https://github.com/open-webui) / [pipelines](https://github.com/open-webui/pipelines) | 2055 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/179994263?s=40&v=4" width="20" height="20" alt=""> [YFGaia](https://github.com/YFGaia) / [dify-plus](https://github.com/YFGaia/dify-plus) | 1734 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/46323662?s=40&v=4" width="20" height="20" alt=""> [TheSpaghettiDetective](https://github.com/TheSpaghettiDetective) / [obico-server](https://github.com/TheSpaghettiDetective/obico-server) | 1687 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/85268109?s=40&v=4" width="20" height="20" alt=""> [MLSysOps](https://github.com/MLSysOps) / [MLE-agent](https://github.com/MLSysOps/MLE-agent) | 1387 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/144196744?s=40&v=4" width="20" height="20" alt=""> [TIGER-AI-Lab](https://github.com/TIGER-AI-Lab) / [TheoremExplainAgent](https://github.com/TIGER-AI-Lab/TheoremExplainAgent) | 1385 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/2314423?s=40&v=4" width="20" height="20" alt=""> [trailofbits](https://github.com/trailofbits) / [buttercup](https://github.com/trailofbits/buttercup) | 1223 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/62564480?s=40&v=4" width="20" height="20" alt=""> [wassim249](https://github.com/wassim249) / [fastapi-langgraph-agent-production-ready-template](https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template) | 1200 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/18422723?s=40&v=4" width="20" height="20" alt=""> [alishobeiri](https://github.com/alishobeiri) / [thread](https://github.com/alishobeiri/thread) | 1098 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/2357342?s=40&v=4" width="20" height="20" alt=""> [dmayboroda](https://github.com/dmayboroda) / [minima](https://github.com/dmayboroda/minima) | 1010 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/65890619?s=40&v=4" width="20" height="20" alt=""> [zstar1003](https://github.com/zstar1003) / [ragflow-plus](https://github.com/zstar1003/ragflow-plus) | 993 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/121352441?s=40&v=4" width="20" height="20" alt=""> [openops-cloud](https://github.com/openops-cloud) / [openops](https://github.com/openops-cloud/openops) | 939 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/151867818?s=40&v=4" width="20" height="20" alt=""> [dynamiq-ai](https://github.com/dynamiq-ai) / [dynamiq](https://github.com/dynamiq-ai/dynamiq) | 927 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/74420131?s=40&v=4" width="20" height="20" alt=""> [xataio](https://github.com/xataio) / [agent](https://github.com/xataio/agent) | 857 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/123981229?s=40&v=4" width="20" height="20" alt=""> [plastic-labs](https://github.com/plastic-labs) / [tutor-gpt](https://github.com/plastic-labs/tutor-gpt) | 845 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/176438967?s=40&v=4" width="20" height="20" alt=""> [trendy-design](https://github.com/trendy-design) / [llmchat](https://github.com/trendy-design/llmchat) | 829 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/2045570?s=40&v=4" width="20" height="20" alt=""> [hotovo](https://github.com/hotovo) / [aider-desk](https://github.com/hotovo/aider-desk) | 781 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/169500408?s=40&v=4" width="20" height="20" alt=""> [opslane](https://github.com/opslane) / [opslane](https://github.com/opslane/opslane) | 719 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/142796018?s=40&v=4" width="20" height="20" alt=""> [wrtnlabs](https://github.com/wrtnlabs) / [autoview](https://github.com/wrtnlabs/autoview) | 688 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/20493493?s=40&v=4" width="20" height="20" alt=""> [andysingal](https://github.com/andysingal) / [llm-course](https://github.com/andysingal/llm-course) | 643 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/48585267?s=40&v=4" width="20" height="20" alt=""> [theopenconversationkit](https://github.com/theopenconversationkit) / [tock](https://github.com/theopenconversationkit/tock) | 587 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/178644984?s=40&v=4" width="20" height="20" alt=""> [sentient-engineering](https://github.com/sentient-engineering) / [agent-q](https://github.com/sentient-engineering/agent-q) | 487 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/58037050?s=40&v=4" width="20" height="20" alt=""> [NicholasGoh](https://github.com/NicholasGoh) / [fastapi-mcp-langgraph-template](https://github.com/NicholasGoh/fastapi-mcp-langgraph-template) | 481 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/148684274?s=40&v=4" width="20" height="20" alt=""> [i-am-alice](https://github.com/i-am-alice) / [3rd-devs](https://github.com/i-am-alice/3rd-devs) | 472 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/163431636?s=40&v=4" width="20" height="20" alt=""> [AIDotNet](https://github.com/AIDotNet) / [koala-ai](https://github.com/AIDotNet/koala-ai) | 470 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/132396805?s=40&v=4" width="20" height="20" alt=""> [phospho-app](https://github.com/phospho-app) / [text-analytics-legacy](https://github.com/phospho-app/text-analytics-legacy) | 439 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/170831637?s=40&v=4" width="20" height="20" alt=""> [inferablehq](https://github.com/inferablehq) / [inferable](https://github.com/inferablehq/inferable) | 403 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/171800766?s=40&v=4" width="20" height="20" alt=""> [duoyang666](https://github.com/duoyang666) / [ai_novel](https://github.com/duoyang666/ai_novel) | 397 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/209155962?s=40&v=4" width="20" height="20" alt=""> [strands-agents](https://github.com/strands-agents) / [samples](https://github.com/strands-agents/samples) | 385 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/16997807?s=40&v=4" width="20" height="20" alt=""> [FranciscoMoretti](https://github.com/FranciscoMoretti) / [sparka](https://github.com/FranciscoMoretti/sparka) | 380 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/66303003?s=40&v=4" width="20" height="20" alt=""> [RobotecAI](https://github.com/RobotecAI) / [rai](https://github.com/RobotecAI/rai) | 373 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/137044109?s=40&v=4" width="20" height="20" alt=""> [ElectricCodeGuy](https://github.com/ElectricCodeGuy) / [SupabaseAuthWithSSR](https://github.com/ElectricCodeGuy/SupabaseAuthWithSSR) | 370 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/169401942?s=40&v=4" width="20" height="20" alt=""> [LibreChat-AI](https://github.com/LibreChat-AI) / [librechat.ai](https://github.com/LibreChat-AI/librechat.ai) | 339 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/15125613?s=40&v=4" width="20" height="20" alt=""> [souzatharsis](https://github.com/souzatharsis) / [tamingLLMs](https://github.com/souzatharsis/tamingLLMs) | 323 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/8931462?s=40&v=4" width="20" height="20" alt=""> [aws-samples](https://github.com/aws-samples) / [aws-ai-ml-workshop-kr](https://github.com/aws-samples/aws-ai-ml-workshop-kr) | 295 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/25676773?s=40&v=4" width="20" height="20" alt=""> [weizxfree](https://github.com/weizxfree) / [KnowFlow](https://github.com/weizxfree/KnowFlow) | 285 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/88676955?s=40&v=4" width="20" height="20" alt=""> [zenml-io](https://github.com/zenml-io) / [zenml-projects](https://github.com/zenml-io/zenml-projects) | 276 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/136071305?s=40&v=4" width="20" height="20" alt=""> [wxai-space](https://github.com/wxai-space) / [LightAgent](https://github.com/wxai-space/LightAgent) | 275 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/90278288?s=40&v=4" width="20" height="20" alt=""> [Ozamatash](https://github.com/Ozamatash) / [deep-research-mcp](https://github.com/Ozamatash/deep-research-mcp) | 269 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/168552753?s=40&v=4" width="20" height="20" alt=""> [sql-agi](https://github.com/sql-agi) / [DB-GPT](https://github.com/sql-agi/DB-GPT) | 241 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/828269?s=40&v=4" width="20" height="20" alt=""> [guyernest](https://github.com/guyernest) / [advanced-rag](https://github.com/guyernest/advanced-rag) | 238 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/175069511?s=40&v=4" width="20" height="20" alt=""> [bklieger-groq](https://github.com/bklieger-groq) / [mathtutor-on-groq](https://github.com/bklieger-groq/mathtutor-on-groq) | 233 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/123981229?s=40&v=4" width="20" height="20" alt=""> [plastic-labs](https://github.com/plastic-labs) / [honcho](https://github.com/plastic-labs/honcho) | 224 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/118967077?s=40&v=4" width="20" height="20" alt=""> [OVINC-CN](https://github.com/OVINC-CN) / [OpenWebUI](https://github.com/OVINC-CN/OpenWebUI) | 202 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/31960995?s=40&v=4" width="20" height="20" alt=""> [zhutoutoutousan](https://github.com/zhutoutoutousan) / [worldquant-miner](https://github.com/zhutoutoutousan/worldquant-miner) | 202 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/29215115?s=40&v=4" width="20" height="20" alt=""> [iceener](https://github.com/iceener) / [ai](https://github.com/iceener/ai) | 186 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/187584218?s=40&v=4" width="20" height="20" alt=""> [giselles-ai](https://github.com/giselles-ai) / [giselle](https://github.com/giselles-ai/giselle) | 181 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/174666116?s=40&v=4" width="20" height="20" alt=""> [ai-shifu](https://github.com/ai-shifu) / [ai-shifu](https://github.com/ai-shifu/ai-shifu) | 181 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/8931462?s=40&v=4" width="20" height="20" alt=""> [aws-samples](https://github.com/aws-samples) / [sample-serverless-mcp-servers](https://github.com/aws-samples/sample-serverless-mcp-servers) | 175 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/202488052?s=40&v=4" width="20" height="20" alt=""> [celerforge](https://github.com/celerforge) / [freenote](https://github.com/celerforge/freenote) | 171 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/104478511?s=40&v=4" width="20" height="20" alt=""> [babelcloud](https://github.com/babelcloud) / [LLM-RGB](https://github.com/babelcloud/LLM-RGB) | 164 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/171735272?s=40&v=4" width="20" height="20" alt=""> [8090-inc](https://github.com/8090-inc) / [xrx-sample-apps](https://github.com/8090-inc/xrx-sample-apps) | 163 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/51827949?s=40&v=4" width="20" height="20" alt=""> [deepset-ai](https://github.com/deepset-ai) / [haystack-core-integrations](https://github.com/deepset-ai/haystack-core-integrations) | 163 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/1009716?s=40&v=4" width="20" height="20" alt=""> [codecentric](https://github.com/codecentric) / [c4-genai-suite](https://github.com/codecentric/c4-genai-suite) | 152 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/196509932?s=40&v=4" width="20" height="20" alt=""> [XSpoonAi](https://github.com/XSpoonAi) / [spoon-core](https://github.com/XSpoonAi/spoon-core) | 150 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/139558948?s=40&v=4" width="20" height="20" alt=""> [chatchat-space](https://github.com/chatchat-space) / [LangGraph-Chatchat](https://github.com/chatchat-space/LangGraph-Chatchat) | 144 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/134601687?s=40&v=4" width="20" height="20" alt=""> [langfuse](https://github.com/langfuse) / [langfuse-docs](https://github.com/langfuse/langfuse-docs) | 139 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/44976328?s=40&v=4" width="20" height="20" alt=""> [piyushgarg-dev](https://github.com/piyushgarg-dev) / [genai-cohort](https://github.com/piyushgarg-dev/genai-cohort) | 135 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/105285801?s=40&v=4" width="20" height="20" alt=""> [i-dot-ai](https://github.com/i-dot-ai) / [redbox](https://github.com/i-dot-ai/redbox) | 132 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/90423581?s=40&v=4" width="20" height="20" alt=""> [bmd1905](https://github.com/bmd1905) / [ChatOpsLLM](https://github.com/bmd1905/ChatOpsLLM) | 127 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/202074444?s=40&v=4" width="20" height="20" alt=""> [Fintech-Dreamer](https://github.com/Fintech-Dreamer) / [FinSynth](https://github.com/Fintech-Dreamer/FinSynth) | 121 |
|
||||
| <img class="avatar mr-2" src="https://avatars.githubusercontent.com/u/1566555?s=40&v=4" width="20" height="20" alt=""> [kenshiro-o](https://github.com/kenshiro-o) / [nagato-ai](https://github.com/kenshiro-o/nagato-ai) | 119 |
|
||||
|
||||
## 🔒 Security & Privacy
|
||||
|
||||
|
||||
@@ -13,3 +13,16 @@
|
||||
### Postgres
|
||||
|
||||
- Most `schema.prisma` changes should produce a change in `packages/shared/prisma/migrations`.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- Environment variables should be imported from the `env.mjs/ts` file of the respective package and not from `process.env.*` to ensure validation and typing.
|
||||
|
||||
## Redis Invocations
|
||||
|
||||
- Highlight usage of `redis.call` invocations. Those may have suboptimal redis cluster routing and will raise errors. Instead, use the native call patterns.
|
||||
Example: `await redis?.call("SET", key, "1", "NX", "EX", TTLSeconds);` should use `await redis?.set(key, "1", "EX", TTLSeconds, "NX");` instead.
|
||||
|
||||
## Langfuse Cloud
|
||||
|
||||
- When attempting to confirm if the current environment is Langfuse Cloud in the frontend, use the `useLangfuseCloudRegion` hook and never environment variables directly.
|
||||
|
||||
+15
-13
@@ -17,11 +17,11 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment: &langfuse-web-env
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
|
||||
NEXTAUTH_SECRET: mysecret
|
||||
SALT: mysalt
|
||||
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" # generate via `openssl rand -hex 32`
|
||||
NEXTAUTH_URL: http://localhost:3000
|
||||
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/postgres}
|
||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-mysecret}
|
||||
SALT: ${SALT:-mysalt}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000} # generate via `openssl rand -hex 32`
|
||||
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000}
|
||||
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
|
||||
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
|
||||
LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
|
||||
@@ -82,8 +82,8 @@ services:
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
CLICKHOUSE_USER: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
|
||||
volumes:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
@@ -103,8 +103,8 @@ services:
|
||||
# create the 'langfuse' bucket before starting the service
|
||||
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
||||
environment:
|
||||
MINIO_ROOT_USER: minio
|
||||
MINIO_ROOT_PASSWORD: miniosecret
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniosecret}
|
||||
ports:
|
||||
- "9090:9000"
|
||||
- "9091:9001"
|
||||
@@ -131,7 +131,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
postgres:
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-17}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
@@ -139,9 +139,11 @@ services:
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-postgres}
|
||||
TZ: UTC
|
||||
PGTZ: UTC
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
|
||||
@@ -4,8 +4,8 @@ services:
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
CLICKHOUSE_USER: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
|
||||
volumes:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
@@ -32,7 +32,7 @@ services:
|
||||
- 6379:6379
|
||||
|
||||
postgres:
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-17}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
@@ -41,9 +41,11 @@ services:
|
||||
retries: 10
|
||||
command: ["postgres", "-c", "log_statement=all"]
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-postgres}
|
||||
- TZ=UTC
|
||||
- PGTZ=UTC
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
|
||||
@@ -4,8 +4,8 @@ services:
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
CLICKHOUSE_USER: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
|
||||
volumes:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
@@ -21,8 +21,8 @@ services:
|
||||
# create the 'langfuse' bucket before starting the service
|
||||
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: minio
|
||||
MINIO_SECRET_KEY: miniosecret
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniosecret}
|
||||
ports:
|
||||
- 127.0.0.1:9090:9000
|
||||
- 127.0.0.1:9091:9001
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
start_period: 1s
|
||||
|
||||
postgres:
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-17}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
@@ -45,9 +45,11 @@ services:
|
||||
retries: 10
|
||||
command: ["postgres", "-c", "log_statement=all"]
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-postgres}
|
||||
- TZ=UTC
|
||||
- PGTZ=UTC
|
||||
ports:
|
||||
- 127.0.0.1:5432:5432
|
||||
volumes:
|
||||
@@ -55,7 +57,7 @@ services:
|
||||
|
||||
# Redis Cluster. Requires a Unix host to work for network_mode: host. I.e. tests will fail on windows and mac with this setup.
|
||||
redis-node-0:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
image: docker.io/bitnamilegacy/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-0:/bitnami/redis/data
|
||||
@@ -65,7 +67,7 @@ services:
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-1:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
image: docker.io/bitnamilegacy/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-1:/bitnami/redis/data
|
||||
@@ -75,7 +77,7 @@ services:
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-2:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
image: docker.io/bitnamilegacy/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-2:/bitnami/redis/data
|
||||
@@ -85,7 +87,7 @@ services:
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-3:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
image: docker.io/bitnamilegacy/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-3:/bitnami/redis/data
|
||||
@@ -95,7 +97,7 @@ services:
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-4:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
image: docker.io/bitnamilegacy/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-4:/bitnami/redis/data
|
||||
@@ -105,7 +107,7 @@ services:
|
||||
REDIS_NODES: 127.0.0.1:6370 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375
|
||||
|
||||
redis-node-5:
|
||||
image: docker.io/bitnami/redis-cluster:8.0
|
||||
image: docker.io/bitnamilegacy/redis-cluster:8.0
|
||||
network_mode: host
|
||||
volumes:
|
||||
- redis-cluster_data-5:/bitnami/redis/data
|
||||
|
||||
+13
-9
@@ -1,11 +1,13 @@
|
||||
services:
|
||||
clickhouse:
|
||||
image: docker.io/clickhouse/clickhouse-server:24.3
|
||||
# Upgrade from 24.3 to check behaviour on new events table.
|
||||
# Unit tests still verify 24.3 behaviour using -azure and -redis-cluster configs.
|
||||
image: docker.io/clickhouse/clickhouse-server:25.8
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
CLICKHOUSE_USER: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
|
||||
volumes:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
@@ -21,8 +23,8 @@ services:
|
||||
# create the 'langfuse' bucket before starting the service
|
||||
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: minio
|
||||
MINIO_SECRET_KEY: miniosecret
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniosecret}
|
||||
ports:
|
||||
- 127.0.0.1:9090:9000
|
||||
- 127.0.0.1:9091:9001
|
||||
@@ -44,7 +46,7 @@ services:
|
||||
- 127.0.0.1:6379:6379
|
||||
|
||||
postgres:
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-17}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
@@ -53,9 +55,11 @@ services:
|
||||
retries: 10
|
||||
command: ["postgres", "-c", "log_statement=all"]
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-postgres}
|
||||
- TZ=UTC
|
||||
- PGTZ=UTC
|
||||
ports:
|
||||
- 127.0.0.1:5432:5432
|
||||
volumes:
|
||||
|
||||
+15
-13
@@ -19,10 +19,10 @@ 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`
|
||||
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000}
|
||||
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/postgres} # CHANGEME
|
||||
SALT: ${SALT:-mysalt} # CHANGEME
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000} # CHANGEME: generate via `openssl rand -hex 32`
|
||||
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
|
||||
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-true}
|
||||
CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL:-clickhouse://clickhouse:9000}
|
||||
@@ -74,7 +74,7 @@ services:
|
||||
- 3000:3000
|
||||
environment:
|
||||
<<: *langfuse-worker-env
|
||||
NEXTAUTH_SECRET: mysecret # CHANGEME
|
||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-mysecret} # CHANGEME
|
||||
LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
|
||||
LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-}
|
||||
LANGFUSE_INIT_PROJECT_ID: ${LANGFUSE_INIT_PROJECT_ID:-}
|
||||
@@ -91,8 +91,8 @@ services:
|
||||
user: "101:101"
|
||||
environment:
|
||||
CLICKHOUSE_DB: default
|
||||
CLICKHOUSE_USER: clickhouse
|
||||
CLICKHOUSE_PASSWORD: clickhouse # CHANGEME
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME
|
||||
volumes:
|
||||
- langfuse_clickhouse_data:/var/lib/clickhouse
|
||||
- langfuse_clickhouse_logs:/var/log/clickhouse-server
|
||||
@@ -113,8 +113,8 @@ services:
|
||||
# create the 'langfuse' bucket before starting the service
|
||||
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
||||
environment:
|
||||
MINIO_ROOT_USER: minio
|
||||
MINIO_ROOT_PASSWORD: miniosecret # CHANGEME
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniosecret} # CHANGEME
|
||||
ports:
|
||||
- 9090:9000
|
||||
- 127.0.0.1:9091:9001
|
||||
@@ -142,7 +142,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
postgres:
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-latest}
|
||||
image: docker.io/postgres:${POSTGRES_VERSION:-17}
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
@@ -150,9 +150,11 @@ services:
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres # CHANGEME
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} # CHANGEME
|
||||
POSTGRES_DB: ${POSTGRES_DB:-postgres}
|
||||
TZ: UTC
|
||||
PGTZ: UTC
|
||||
ports:
|
||||
- 127.0.0.1:5432:5432
|
||||
volumes:
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": "20"
|
||||
"node": "24"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
@@ -27,14 +27,14 @@
|
||||
"@langfuse/shared": "workspace:*",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"next": "^14.2.30",
|
||||
"next": "15.5.4",
|
||||
"next-auth": "^4.24.11",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/node": "^20.11.29",
|
||||
"@types/node": "^24.3.0",
|
||||
"@typescript-eslint/parser": "^7.12.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"lib": ["ES2020"],
|
||||
"lib": ["es2023"],
|
||||
"outDir": "./dist",
|
||||
"types": ["node"],
|
||||
"target": "ES2020",
|
||||
"target": "es2024",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["."],
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public/integrations/blob-storage
|
||||
endpoints:
|
||||
getBlobStorageIntegrations:
|
||||
docs: Get all blob storage integrations for the organization (requires organization-scoped API key)
|
||||
method: GET
|
||||
path: ""
|
||||
response: BlobStorageIntegrationsResponse
|
||||
|
||||
upsertBlobStorageIntegration:
|
||||
docs: Create or update a blob storage integration for a specific project (requires organization-scoped API key). The configuration is validated by performing a test upload to the bucket.
|
||||
method: PUT
|
||||
path: ""
|
||||
request: CreateBlobStorageIntegrationRequest
|
||||
response: BlobStorageIntegrationResponse
|
||||
|
||||
deleteBlobStorageIntegration:
|
||||
docs: Delete a blob storage integration by ID (requires organization-scoped API key)
|
||||
method: DELETE
|
||||
path: "/{id}"
|
||||
path-parameters:
|
||||
id: string
|
||||
response: BlobStorageIntegrationDeletionResponse
|
||||
|
||||
types:
|
||||
BlobStorageIntegrationType:
|
||||
enum:
|
||||
- S3
|
||||
- S3_COMPATIBLE
|
||||
- AZURE_BLOB_STORAGE
|
||||
|
||||
BlobStorageIntegrationFileType:
|
||||
enum:
|
||||
- JSON
|
||||
- CSV
|
||||
- JSONL
|
||||
|
||||
BlobStorageExportMode:
|
||||
enum:
|
||||
- FULL_HISTORY
|
||||
- FROM_TODAY
|
||||
- FROM_CUSTOM_DATE
|
||||
|
||||
BlobStorageExportFrequency:
|
||||
enum:
|
||||
- hourly
|
||||
- daily
|
||||
- weekly
|
||||
|
||||
CreateBlobStorageIntegrationRequest:
|
||||
properties:
|
||||
projectId:
|
||||
type: string
|
||||
docs: ID of the project in which to configure the blob storage integration
|
||||
type: BlobStorageIntegrationType
|
||||
bucketName:
|
||||
type: string
|
||||
docs: Name of the storage bucket
|
||||
endpoint:
|
||||
type: optional<string>
|
||||
docs: Custom endpoint URL (required for S3_COMPATIBLE type)
|
||||
region:
|
||||
type: string
|
||||
docs: Storage region
|
||||
accessKeyId:
|
||||
type: optional<string>
|
||||
docs: Access key ID for authentication
|
||||
secretAccessKey:
|
||||
type: optional<string>
|
||||
docs: Secret access key for authentication (will be encrypted when stored)
|
||||
prefix:
|
||||
type: optional<string>
|
||||
docs: Path prefix for exported files (must end with forward slash if provided)
|
||||
exportFrequency: BlobStorageExportFrequency
|
||||
enabled:
|
||||
type: boolean
|
||||
docs: Whether the integration is active
|
||||
forcePathStyle:
|
||||
type: boolean
|
||||
docs: Use path-style URLs for S3 requests
|
||||
fileType: BlobStorageIntegrationFileType
|
||||
exportMode: BlobStorageExportMode
|
||||
exportStartDate:
|
||||
type: optional<datetime>
|
||||
docs: Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE)
|
||||
|
||||
BlobStorageIntegrationResponse:
|
||||
properties:
|
||||
id: string
|
||||
projectId: string
|
||||
type: BlobStorageIntegrationType
|
||||
bucketName: string
|
||||
endpoint: optional<string>
|
||||
region: string
|
||||
accessKeyId: optional<string>
|
||||
prefix: string
|
||||
exportFrequency: BlobStorageExportFrequency
|
||||
enabled: boolean
|
||||
forcePathStyle: boolean
|
||||
fileType: BlobStorageIntegrationFileType
|
||||
exportMode: BlobStorageExportMode
|
||||
exportStartDate: optional<datetime>
|
||||
nextSyncAt: optional<datetime>
|
||||
lastSyncAt: optional<datetime>
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
|
||||
BlobStorageIntegrationsResponse:
|
||||
properties:
|
||||
data: list<BlobStorageIntegrationResponse>
|
||||
|
||||
BlobStorageIntegrationDeletionResponse:
|
||||
properties:
|
||||
message: string
|
||||
@@ -8,8 +8,9 @@ service:
|
||||
endpoints:
|
||||
batch:
|
||||
docs: |
|
||||
Batched ingestion for Langfuse Tracing.
|
||||
If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement.
|
||||
**Legacy endpoint for batch ingestion for Langfuse Observability.**
|
||||
|
||||
-> Please use the OpenTelemetry endpoint (`/api/public/otel`). Learn more: https://langfuse.com/integrations/native/opentelemetry
|
||||
|
||||
Within each batch, there can be multiple events.
|
||||
Each event has a type, an id, a timestamp, metadata and a body.
|
||||
|
||||
@@ -35,6 +35,18 @@ service:
|
||||
type: string
|
||||
docs: The unique langfuse identifier of a score config
|
||||
response: commons.ScoreConfig
|
||||
|
||||
update:
|
||||
docs: Update a score config
|
||||
method: PATCH
|
||||
path: /score-configs/{configId}
|
||||
path-parameters:
|
||||
configId:
|
||||
type: string
|
||||
docs: The unique langfuse identifier of a score config
|
||||
request: UpdateScoreConfigRequest
|
||||
response: commons.ScoreConfig
|
||||
|
||||
types:
|
||||
ScoreConfigs:
|
||||
properties:
|
||||
@@ -56,3 +68,23 @@ types:
|
||||
description:
|
||||
type: optional<string>
|
||||
docs: Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage
|
||||
UpdateScoreConfigRequest:
|
||||
properties:
|
||||
isArchived:
|
||||
type: optional<boolean>
|
||||
docs: The status of the score config showing if it is archived or not
|
||||
name:
|
||||
type: optional<string>
|
||||
docs: The name of the score config
|
||||
categories:
|
||||
type: optional<list<commons.ConfigCategory>>
|
||||
docs: Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed
|
||||
minValue:
|
||||
type: optional<double>
|
||||
docs: Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞
|
||||
maxValue:
|
||||
type: optional<double>
|
||||
docs: Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞
|
||||
description:
|
||||
type: optional<string>
|
||||
docs: Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage
|
||||
|
||||
@@ -50,6 +50,9 @@ service:
|
||||
configId:
|
||||
type: optional<string>
|
||||
docs: Retrieve only scores with a specific configId.
|
||||
sessionId:
|
||||
type: optional<string>
|
||||
docs: Retrieve only scores with a specific sessionId.
|
||||
queueId:
|
||||
type: optional<string>
|
||||
docs: Retrieve only scores with a specific annotation queueId.
|
||||
|
||||
@@ -65,7 +65,33 @@ service:
|
||||
docs: Optional filter for traces where the environment is one of the provided values.
|
||||
fields:
|
||||
type: optional<string>
|
||||
docs: "Comma-separated list of fields to include in the response. Available field groups are 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not provided, all fields are included. Example: 'core,scores,metrics'"
|
||||
docs: "Comma-separated list of fields to include in the response. Available field groups: 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not specified, all fields are returned. Example: 'core,scores,metrics'. Note: Excluded 'observations' or 'scores' fields return empty arrays; excluded 'metrics' returns -1 for 'totalCost' and 'latency'."
|
||||
filter:
|
||||
type: optional<string>
|
||||
docs: |
|
||||
JSON string containing an array of filter conditions. When provided, this takes precedence over legacy filter parameters (userId, name, sessionId, tags, version, release, environment, fromTimestamp, toTimestamp).
|
||||
Each filter condition has the following structure:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null"
|
||||
"column": string, // Required. Column to filter on
|
||||
"operator": string, // Required. Operator based on type:
|
||||
// - datetime: ">", "<", ">=", "<="
|
||||
// - string: "=", "contains", "does not contain", "starts with", "ends with"
|
||||
// - stringOptions: "any of", "none of"
|
||||
// - categoryOptions: "any of", "none of"
|
||||
// - arrayOptions: "any of", "none of", "all of"
|
||||
// - number: "=", ">", "<", ">=", "<="
|
||||
// - stringObject: "=", "contains", "does not contain", "starts with", "ends with"
|
||||
// - numberObject: "=", ">", "<", ">=", "<="
|
||||
// - boolean: "=", "<>"
|
||||
// - null: "is null", "is not null"
|
||||
"value": any, // Required (except for null type). Value to compare against. Type depends on filter type
|
||||
"key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata
|
||||
}
|
||||
]
|
||||
```
|
||||
response: Traces
|
||||
deleteMultiple:
|
||||
docs: Delete multiple traces
|
||||
|
||||
@@ -31,7 +31,7 @@ groups:
|
||||
# client-class-name: LangfuseClient
|
||||
|
||||
- name: fernapi/fern-typescript-node-sdk
|
||||
version: 2.6.1
|
||||
version: 2.12.3
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../generated/typescript
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"organization": "langfuse",
|
||||
"version": "0.56.0"
|
||||
}
|
||||
"version": "0.77.5"
|
||||
}
|
||||
+8
-8
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "langfuse",
|
||||
"version": "3.103.0",
|
||||
"version": "3.117.2",
|
||||
"author": "engineering@langfuse.com",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "20"
|
||||
"node": "24"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -24,8 +24,8 @@
|
||||
"start": "turbo run start",
|
||||
"dev": "turbo run dev",
|
||||
"dev:worker": "turbo run dev --filter=worker",
|
||||
"dev:web": "turbo run dev --filter=web",
|
||||
"dev:web-turbo": "turbo run dev --filter=web -- --turbo",
|
||||
"dev:web": "turbo run dev --filter=web -- --turbo",
|
||||
"dev:web-no-turbo": "turbo run dev --filter=web",
|
||||
"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",
|
||||
@@ -36,11 +36,11 @@
|
||||
"devDependencies": {
|
||||
"@release-it/bumper": "^7.0.5",
|
||||
"braces": "3.0.3",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"husky": "^9.0.11",
|
||||
"dotenv-cli": "^7.4.4",
|
||||
"husky": "^9.1.7",
|
||||
"prettier": "^3.6.2",
|
||||
"release-it": "^19.0.3",
|
||||
"turbo": "^2.5.6"
|
||||
"release-it": "^19.0.4",
|
||||
"turbo": "^2.5.8"
|
||||
},
|
||||
"release-it": {
|
||||
"git": {
|
||||
|
||||
@@ -2,10 +2,15 @@ const { resolve } = require("node:path");
|
||||
|
||||
const project = resolve(process.cwd(), "tsconfig.json");
|
||||
|
||||
// Handle eslint-config-turbo's default export
|
||||
const turboConfig = require("eslint-config-turbo");
|
||||
const turboConfigToUse = turboConfig.default || turboConfig;
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
extends: ["eslint:recommended", "prettier", "eslint-config-turbo"],
|
||||
plugins: ["only-warn"],
|
||||
// extends: ["eslint:recommended", "prettier"require().default],
|
||||
extends: ["eslint:recommended", "prettier"],
|
||||
plugins: ["only-warn", "turbo"],
|
||||
globals: {
|
||||
React: true,
|
||||
JSX: true,
|
||||
@@ -30,6 +35,7 @@ module.exports = {
|
||||
rules: {
|
||||
"no-redeclare": "off",
|
||||
"import/order": "off",
|
||||
...(turboConfigToUse.rules || {}),
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@@ -51,5 +57,6 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
},
|
||||
...(turboConfigToUse.overrides || []),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -2,6 +2,9 @@ const { resolve } = require("node:path");
|
||||
|
||||
const project = resolve(process.cwd(), "tsconfig.json");
|
||||
|
||||
const turboConfig = require("eslint-config-turbo");
|
||||
const turboConfigToUse = turboConfig.default || turboConfig;
|
||||
|
||||
/*
|
||||
* This is a custom ESLint configuration for use with
|
||||
* Next.js apps.
|
||||
@@ -16,7 +19,6 @@ module.exports = {
|
||||
extends: [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/strict-type-checked",
|
||||
"eslint-config-turbo",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
@@ -47,6 +49,7 @@ module.exports = {
|
||||
ignorePatterns: ["node_modules/", "dist/"],
|
||||
// add rules configurations here
|
||||
rules: {
|
||||
...(turboConfigToUse.rules || {}),
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"warn",
|
||||
{
|
||||
|
||||
@@ -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.6",
|
||||
"eslint-config-turbo": "^2.5.8",
|
||||
"eslint-plugin-only-warn": "^1.1.0",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
|
||||
@@ -3,9 +3,17 @@
|
||||
"display": "Next.js",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"plugins": [{ "name": "next" }],
|
||||
"target": "es2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"target": "es2024",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"skipLibCheck": true,
|
||||
@@ -18,8 +26,16 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"types": ["jest", "node"],
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
],
|
||||
},
|
||||
"include": ["src", "next-env.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
"include": [
|
||||
"src",
|
||||
"next-env.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
-- Recreate materialized views for project_environments table
|
||||
CREATE MATERIALIZED VIEW project_environments_traces_mv ON CLUSTER default TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM traces
|
||||
GROUP BY project_id;
|
||||
|
||||
CREATE MATERIALIZED VIEW project_environments_observations_mv ON CLUSTER default TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM observations
|
||||
GROUP BY project_id;
|
||||
|
||||
CREATE MATERIALIZED VIEW project_environments_scores_mv ON CLUSTER default TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM scores
|
||||
GROUP BY project_id;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-- Drop materialized views feeding project_environments table
|
||||
DROP VIEW IF EXISTS project_environments_traces_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS project_environments_observations_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS project_environments_scores_mv ON CLUSTER default;
|
||||
@@ -0,0 +1,114 @@
|
||||
-- Recreate materialized views derived from traces_null table
|
||||
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 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 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,4 @@
|
||||
-- Drop materialized views derived from traces_null table
|
||||
DROP VIEW IF EXISTS traces_all_amt_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS traces_7d_amt_mv ON CLUSTER default;
|
||||
DROP VIEW IF EXISTS traces_30d_amt_mv ON CLUSTER default;
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
-- Recreate traces_null and trace amt tables
|
||||
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 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 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 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;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- Drop traces_null and trace amt tables
|
||||
DROP TABLE IF EXISTS traces_null ON CLUSTER default;
|
||||
DROP TABLE IF EXISTS traces_all_amt ON CLUSTER default;
|
||||
DROP TABLE IF EXISTS traces_7d_amt ON CLUSTER default;
|
||||
DROP TABLE IF EXISTS traces_30d_amt ON CLUSTER default;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
-- Recreate materialized views for project_environments table
|
||||
CREATE MATERIALIZED VIEW project_environments_traces_mv TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM traces
|
||||
GROUP BY project_id;
|
||||
|
||||
CREATE MATERIALIZED VIEW project_environments_observations_mv TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM observations
|
||||
GROUP BY project_id;
|
||||
|
||||
CREATE MATERIALIZED VIEW project_environments_scores_mv TO project_environments AS
|
||||
SELECT
|
||||
project_id,
|
||||
groupUniqArray(environment) AS environments
|
||||
FROM scores
|
||||
GROUP BY project_id;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-- Drop materialized views feeding project_environments table
|
||||
DROP VIEW IF EXISTS project_environments_traces_mv;
|
||||
DROP VIEW IF EXISTS project_environments_observations_mv;
|
||||
DROP VIEW IF EXISTS project_environments_scores_mv;
|
||||
@@ -0,0 +1,114 @@
|
||||
-- Recreate materialized views derived from traces_null table
|
||||
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 -- 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 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 -- 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 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 -- 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,4 @@
|
||||
-- Drop materialized views derived from traces_null table
|
||||
DROP VIEW IF EXISTS traces_all_amt_mv;
|
||||
DROP VIEW IF EXISTS traces_7d_amt_mv;
|
||||
DROP VIEW IF EXISTS traces_30d_amt_mv;
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
-- Recreate traces_null and trace amt tables
|
||||
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 -- 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 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 -- 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 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 -- 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 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 -- 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;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- Drop traces_null and trace amt tables
|
||||
DROP TABLE IF EXISTS traces_null;
|
||||
DROP TABLE IF EXISTS traces_all_amt;
|
||||
DROP TABLE IF EXISTS traces_7d_amt;
|
||||
DROP TABLE IF EXISTS traces_30d_amt;
|
||||
@@ -0,0 +1,261 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Development-only ClickHouse table creation script
|
||||
# This script is for creating experimental/development tables that are not yet
|
||||
# ready to be part of the official migration system.
|
||||
#
|
||||
# Usage:
|
||||
# pnpm run ch:dev-tables (from packages/shared/)
|
||||
#
|
||||
# This script is automatically run as part of:
|
||||
# - pnpm run dx
|
||||
# - pnpm run dx-f
|
||||
# - pnpm run ch:reset
|
||||
|
||||
# Load environment variables
|
||||
[ -f ../../.env ] && source ../../.env
|
||||
|
||||
# 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
|
||||
|
||||
# Ensure CLICKHOUSE_DB is set
|
||||
if [ -z "${CLICKHOUSE_DB}" ]; then
|
||||
export CLICKHOUSE_DB="default"
|
||||
fi
|
||||
|
||||
# Parse the CLICKHOUSE_MIGRATION_URL to extract host and port
|
||||
# Expected format: clickhouse://localhost:9000
|
||||
if [[ $CLICKHOUSE_MIGRATION_URL =~ ^clickhouse://([^:]+):([0-9]+)$ ]]; then
|
||||
CLICKHOUSE_HOST="${BASH_REMATCH[1]}"
|
||||
CLICKHOUSE_PORT="${BASH_REMATCH[2]}"
|
||||
elif [[ $CLICKHOUSE_MIGRATION_URL =~ ^clickhouse://([^:]+)$ ]]; then
|
||||
CLICKHOUSE_HOST="${BASH_REMATCH[1]}"
|
||||
CLICKHOUSE_PORT="9000" # Default native protocol port
|
||||
else
|
||||
echo "Error: Could not parse CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v clickhouse &> /dev/null
|
||||
then
|
||||
echo "Error: clickhouse binary could not be found. Please install ClickHouse client tools."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Creating development tables in ClickHouse..."
|
||||
|
||||
# Execute the CREATE TABLE statements
|
||||
# Add your development tables here using CREATE TABLE IF NOT EXISTS
|
||||
|
||||
clickhouse client \
|
||||
--host="${CLICKHOUSE_HOST}" \
|
||||
--port="${CLICKHOUSE_PORT}" \
|
||||
--user="${CLICKHOUSE_USER}" \
|
||||
--password="${CLICKHOUSE_PASSWORD}" \
|
||||
--database="${CLICKHOUSE_DB}" \
|
||||
--multiquery <<EOF
|
||||
-- Create new events table for development setups.
|
||||
-- We expect this to be fully immutable and eventually replace observations.
|
||||
-- See LFE-5394 for ongoing discussion.
|
||||
-- Remove IF NOT EXISTS when moving this to prod migrations.
|
||||
CREATE TABLE IF NOT EXISTS events
|
||||
(
|
||||
org_id String, -- TODO: Unsure about this one
|
||||
project_id String,
|
||||
trace_id String,
|
||||
span_id String,
|
||||
parent_span_id Nullable(String),
|
||||
|
||||
start_time DateTime64(6),
|
||||
end_time Nullable(DateTime64(6)),
|
||||
|
||||
-- Core properties
|
||||
name String,
|
||||
type LowCardinality(String),
|
||||
environment LowCardinality(String) DEFAULT 'default',
|
||||
version Nullable(String),
|
||||
|
||||
user_id String,
|
||||
session_id String,
|
||||
|
||||
level LowCardinality(String),
|
||||
status_message String, -- Threat '' and null the same for search
|
||||
completion_start_time Nullable(DateTime64(6)),
|
||||
|
||||
-- Prompt
|
||||
prompt_id Nullable(String),
|
||||
prompt_name Nullable(String),
|
||||
prompt_version Nullable(String),
|
||||
|
||||
-- Model
|
||||
model_id Nullable(String),
|
||||
provided_model_name Nullable(String),
|
||||
model_parameters Nullable(String),
|
||||
|
||||
-- Usage
|
||||
provided_usage_details Map(LowCardinality(String), UInt64),
|
||||
usage_details Map(LowCardinality(String), UInt64),
|
||||
provided_cost_details Map(LowCardinality(String), Decimal(18, 12)),
|
||||
cost_details Map(LowCardinality(String), Decimal(18, 12)),
|
||||
total_cost Decimal(18,12), -- 0 if not provided
|
||||
|
||||
-- I/O
|
||||
input String CODEC(ZSTD(3)),
|
||||
output String CODEC(ZSTD(3)),
|
||||
|
||||
-- TODO Metadata: Decide for approach
|
||||
-- -- Approach 1: Use plain JSON type with default config
|
||||
metadata JSON(max_dynamic_paths=1024, max_dynamic_types=32),
|
||||
-- -- Approach 2: Uses ideas from https://www.uber.com/en-DE/blog/logging/
|
||||
-- -- but uses Dynamic type to make this a single list
|
||||
metadata_names Array(String),
|
||||
metadata_values Array(Dynamic(max_types=32)),
|
||||
-- -- Approach 3: 1:1 copy of https://www.uber.com/en-DE/blog/logging/
|
||||
-- -- May require further high-level types and lots of thought during
|
||||
-- -- write and query-time.
|
||||
metadata_string_names Array(String),
|
||||
metadata_string_values Array(String),
|
||||
metadata_number_names Array(String),
|
||||
metadata_number_values Array(Float64),
|
||||
metadata_bool_names Array(String),
|
||||
metadata_bool_values Array(UInt8),
|
||||
|
||||
-- Source metadata (Instrumentation)
|
||||
source LowCardinality(String),
|
||||
service_name Nullable(String),
|
||||
service_version Nullable(String),
|
||||
scope_name Nullable(String),
|
||||
scope_version Nullable(String),
|
||||
telemetry_sdk_language Nullable(String),
|
||||
telemetry_sdk_name Nullable(String),
|
||||
telemetry_sdk_version Nullable(String),
|
||||
|
||||
-- Generic props
|
||||
blob_storage_file_path String,
|
||||
event_raw String,
|
||||
event_bytes UInt64,
|
||||
created_at DateTime64(6) DEFAULT now(),
|
||||
updated_at DateTime64(6) DEFAULT now(),
|
||||
event_ts DateTime64(6),
|
||||
is_deleted UInt8,
|
||||
|
||||
-- Indexes
|
||||
INDEX idx_span_id span_id TYPE bloom_filter(0.01) GRANULARITY 1,
|
||||
INDEX idx_trace_id trace_id TYPE bloom_filter(0.01) GRANULARITY 1,
|
||||
INDEX idx_type type TYPE set(50) GRANULARITY 1,
|
||||
INDEX idx_created_at created_at TYPE minmax GRANULARITY 1,
|
||||
INDEX idx_updated_at updated_at TYPE minmax GRANULARITY 1,
|
||||
|
||||
-- Full Text Search Indexes (We should try different index sizes, e.g. 2048, 4096, or 8192)
|
||||
INDEX idx_fts_input_1 input TYPE ngrambf_v1(1, 1024, 1, 0) GRANULARITY 1,
|
||||
INDEX idx_fts_input_2 input TYPE ngrambf_v1(2, 1024, 1, 0) GRANULARITY 1,
|
||||
INDEX idx_fts_input_4 input TYPE ngrambf_v1(4, 1024, 1, 0) GRANULARITY 1,
|
||||
INDEX idx_fts_input_8 input TYPE ngrambf_v1(8, 1024, 1, 0) GRANULARITY 1,
|
||||
|
||||
INDEX idx_fts_output_1 output TYPE ngrambf_v1(1, 1024, 1, 0) GRANULARITY 1,
|
||||
INDEX idx_fts_output_2 output TYPE ngrambf_v1(2, 1024, 1, 0) GRANULARITY 1,
|
||||
INDEX idx_fts_output_4 output TYPE ngrambf_v1(4, 1024, 1, 0) GRANULARITY 1,
|
||||
INDEX idx_fts_output_8 output TYPE ngrambf_v1(8, 1024, 1, 0) GRANULARITY 1,
|
||||
)
|
||||
ENGINE = ReplacingMergeTree(event_ts, is_deleted)
|
||||
-- ENGINE = (Replicated)ReplacingMergeTree(event_ts, is_deleted)
|
||||
PARTITION BY toYYYYMM(start_time)
|
||||
ORDER BY (project_id, toUnixTimestamp(start_time), trace_id, span_id)
|
||||
EOF
|
||||
|
||||
echo "Populating development tables with sample data..."
|
||||
|
||||
clickhouse client \
|
||||
--host="${CLICKHOUSE_HOST}" \
|
||||
--port="${CLICKHOUSE_PORT}" \
|
||||
--user="${CLICKHOUSE_USER}" \
|
||||
--password="${CLICKHOUSE_PASSWORD}" \
|
||||
--database="${CLICKHOUSE_DB}" \
|
||||
--multiquery <<EOF
|
||||
TRUNCATE events;
|
||||
INSERT INTO events (org_id, project_id, trace_id, span_id, parent_span_id, start_time, end_time, name, type,
|
||||
environment, version, user_id, session_id, level, status_message, completion_start_time, prompt_id,
|
||||
prompt_name, prompt_version, model_id, provided_model_name, model_parameters,
|
||||
provided_usage_details, usage_details, provided_cost_details, cost_details, total_cost, input,
|
||||
output, metadata, metadata_names, metadata_values, metadata_string_names, metadata_string_values,
|
||||
metadata_number_names, metadata_number_values, metadata_bool_names, metadata_bool_values, source,
|
||||
service_name, service_version, scope_name, scope_version, telemetry_sdk_language,
|
||||
telemetry_sdk_name, telemetry_sdk_version, blob_storage_file_path, event_raw, event_bytes,
|
||||
created_at, updated_at, event_ts, is_deleted)
|
||||
SELECT concat('o', project_id) AS org_id,
|
||||
project_id,
|
||||
trace_id,
|
||||
id AS span_id,
|
||||
parent_observation_id AS parent_span_id,
|
||||
start_time,
|
||||
end_time,
|
||||
name,
|
||||
type,
|
||||
environment,
|
||||
version,
|
||||
concat('u_', floor(randUniform(1, 100))) AS user_id,
|
||||
concat('s_', floor(randUniform(1, 100))) AS session_id,
|
||||
level,
|
||||
ifNull(status_message, '') AS status_message,
|
||||
completion_start_time,
|
||||
prompt_id,
|
||||
prompt_name,
|
||||
CAST(prompt_version, 'Nullable(String)'),
|
||||
internal_model_id AS model_id,
|
||||
provided_model_name,
|
||||
model_parameters,
|
||||
provided_usage_details,
|
||||
usage_details,
|
||||
provided_cost_details,
|
||||
cost_details,
|
||||
ifNull(total_cost, 0) AS total_cost,
|
||||
ifNull(input, '') AS input,
|
||||
ifNull(output, '') AS output,
|
||||
CAST(metadata, 'JSON'),
|
||||
mapKeys(metadata) AS \`metadata.names\`,
|
||||
mapValues(metadata) AS \`metadata.values\`,
|
||||
mapKeys(metadata) AS metadata_string_names,
|
||||
mapValues(metadata) AS metadata_string_values,
|
||||
[] AS metadata_number_names,
|
||||
[] AS metadata_number_values,
|
||||
[] AS metadata_bool_names,
|
||||
[] AS metadata_bool_values,
|
||||
multiIf(mapContains(metadata, 'resourceAttributes'), 'otel', 'ingestion-api') AS source,
|
||||
NULL AS service_name,
|
||||
NULL AS service_version,
|
||||
NULL AS scope_name,
|
||||
NULL AS scope_version,
|
||||
NULL AS telemetry_sdk_language,
|
||||
NULL AS telemetry_sdk_name,
|
||||
NULL AS telemetry_sdk_version,
|
||||
'' AS blob_storage_file_path,
|
||||
'' AS event_raw,
|
||||
0 AS event_bytes,
|
||||
created_at,
|
||||
updated_at,
|
||||
event_ts,
|
||||
is_deleted
|
||||
FROM observations
|
||||
WHERE (is_deleted = 0);
|
||||
EOF
|
||||
|
||||
echo "Development tables created successfully (or already exist)."
|
||||
echo ""
|
||||
@@ -6,7 +6,7 @@
|
||||
"main": "./dist/src/index.js",
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"engines": {
|
||||
"node": "20"
|
||||
"node": "24"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -47,7 +47,8 @@
|
||||
"ch:up": "bash clickhouse/scripts/up.sh",
|
||||
"ch:down": "bash clickhouse/scripts/down.sh",
|
||||
"ch:drop": "bash clickhouse/scripts/drop.sh",
|
||||
"ch:reset": "pnpm run ch:down && pnpm run ch:up && pnpm run ch:seed",
|
||||
"ch:dev-tables": "bash clickhouse/scripts/dev-tables.sh",
|
||||
"ch:reset": "pnpm run ch:down && pnpm run ch:up && pnpm run ch:seed && pnpm run ch:dev-tables",
|
||||
"ch:seed": "dotenv -e ../../.env -- ts-node -r tsconfig-paths/register -r dotenv/config --compiler-options '{\"module\":\"CommonJS\"}' scripts/seeder/seed-clickhouse.ts",
|
||||
"load:setup": "dotenv -e ../../.env -- tsx scripts/seeder/load-seed-clickhouse.ts"
|
||||
},
|
||||
@@ -63,7 +64,7 @@
|
||||
"@azure/storage-blob": "^12.26.0",
|
||||
"@clickhouse/client": "^1.12.1",
|
||||
"@google-cloud/storage": "^7.17.0",
|
||||
"@langchain/anthropic": "^0.3.22",
|
||||
"@langchain/anthropic": "^0.3.29",
|
||||
"@langchain/aws": "^0.1.11",
|
||||
"@langchain/core": "^0.3.58",
|
||||
"@langchain/google-genai": "^0.2.12",
|
||||
@@ -71,21 +72,23 @@
|
||||
"@langchain/openai": "^0.5.13",
|
||||
"@opentelemetry/api": ">=1.0.0 <1.10.0",
|
||||
"@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",
|
||||
"@react-email/components": "^0.5.1",
|
||||
"@react-email/render": "^1.2.1",
|
||||
"@slack/oauth": "^3.0.4",
|
||||
"@slack/web-api": "^7.10.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bullmq": "^5.34.10",
|
||||
"dd-trace": "^5.36.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"dd-trace": "^5.65.0",
|
||||
"decimal.js": "^10.4.3",
|
||||
"exponential-backoff": "^3.1.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"ioredis": "^5.4.1",
|
||||
"ipaddr.js": "^2.2.0",
|
||||
"jsonpath-plus": "10.3.0",
|
||||
"kysely": "^0.27.4",
|
||||
"langchain": "^0.3.28",
|
||||
"langchain": "^0.3.30",
|
||||
"langfuse-langchain": "3.38.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lossless-json": "^4.1.1",
|
||||
@@ -101,10 +104,10 @@
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/node": "^20.11.29",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/nodemailer": "^6.4.16",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/react": "18.2.79",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/parser": "^7.12.0",
|
||||
"eslint": "^8.57.0",
|
||||
@@ -112,7 +115,6 @@
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"kysely-codegen": "^0.16.8",
|
||||
"nodemon": "^3.1.7",
|
||||
"prettier": "^3.6.2",
|
||||
"prisma": "^6.10.1",
|
||||
"prisma-erd-generator": "^1.11.2",
|
||||
@@ -122,7 +124,7 @@
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "~18.2.79",
|
||||
"react": "~18.2.0"
|
||||
"@types/react": "~19.2.2",
|
||||
"react": "~19.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +332,15 @@ export type BlobStorageIntegration = {
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type CloudSpendAlert = {
|
||||
id: string;
|
||||
org_id: string;
|
||||
title: string;
|
||||
threshold: string;
|
||||
triggered_at: Timestamp | null;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type Comment = {
|
||||
id: string;
|
||||
project_id: string;
|
||||
@@ -641,6 +650,11 @@ export type Organization = {
|
||||
updated_at: Generated<Timestamp>;
|
||||
cloud_config: unknown | null;
|
||||
metadata: unknown | null;
|
||||
cloud_billing_cycle_anchor: Generated<Timestamp | null>;
|
||||
cloud_billing_cycle_updated_at: Timestamp | null;
|
||||
cloud_current_cycle_usage: number | null;
|
||||
cloud_free_tier_usage_threshold_state: string | null;
|
||||
ai_features_enabled: Generated<boolean>;
|
||||
};
|
||||
export type OrganizationMembership = {
|
||||
id: string;
|
||||
@@ -846,6 +860,7 @@ export type DB = {
|
||||
batch_exports: BatchExport;
|
||||
billing_meter_backups: BillingMeterBackup;
|
||||
blob_storage_integrations: BlobStorageIntegration;
|
||||
cloud_spend_alerts: CloudSpendAlert;
|
||||
comments: Comment;
|
||||
cron_jobs: CronJobs;
|
||||
dashboard_widgets: DashboardWidget;
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "organizations" ADD COLUMN "ai_features_enabled" BOOLEAN NOT NULL DEFAULT false;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS "job_executions_project_id_job_output_score_id_idx" ON "job_executions"("project_id", "job_output_score_id");
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "organizations" ADD COLUMN "cloud_billing_cycle_anchor" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "cloud_billing_cycle_updated_at" TIMESTAMP(3),
|
||||
ADD COLUMN "cloud_current_cycle_usage" INTEGER,
|
||||
ADD COLUMN "cloud_free_tier_usage_threshold_state" TEXT;
|
||||
|
||||
-- Backfill cloud_billing_cycle_anchor for existing organizations:
|
||||
-- Step 1: Set to NULL for orgs WITH active subscriptions (will be backfilled from Stripe by worker job)
|
||||
UPDATE "organizations"
|
||||
SET "cloud_billing_cycle_anchor" = NULL;
|
||||
|
||||
-- Step 2: Set to created_at for orgs WITHOUT active subscriptions (free tier orgs)
|
||||
UPDATE "organizations"
|
||||
SET "cloud_billing_cycle_anchor" = "created_at"
|
||||
WHERE
|
||||
"cloud_config" IS NULL
|
||||
OR NOT (
|
||||
"cloud_config"::jsonb -> 'stripe' ? 'activeSubscriptionId'
|
||||
AND "cloud_config"::jsonb -> 'stripe' ->> 'activeSubscriptionId' IS NOT NULL
|
||||
AND "cloud_config"::jsonb -> 'stripe' ->> 'activeSubscriptionId' != ''
|
||||
);
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
INSERT INTO background_migrations (id, name, script, args)
|
||||
VALUES ('0199b890-1093-7d1f-b662-be3c03527e93', '20250102_backfill_billing_cycle_anchors', 'backfillBillingCycleAnchors', '{}');
|
||||
@@ -0,0 +1,15 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "cloud_spend_alerts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"org_id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"threshold" DECIMAL(65,30) NOT NULL,
|
||||
"triggered_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "cloud_spend_alerts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "cloud_spend_alerts" ADD CONSTRAINT "cloud_spend_alerts_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX CONCURRENTLY "cloud_spend_alerts_org_id_idx" ON "cloud_spend_alerts"("org_id");
|
||||
|
||||
@@ -104,17 +104,23 @@ model VerificationToken {
|
||||
}
|
||||
|
||||
model Organization {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
cloudConfig Json? @map("cloud_config") // Langfuse Cloud, for zod schema see @/src/features/organizations/utils/cloudConfigSchema
|
||||
metadata Json?
|
||||
organizationMemberships OrganizationMembership[]
|
||||
projects Project[]
|
||||
MembershipInvitation MembershipInvitation[]
|
||||
ApiKey ApiKey[]
|
||||
surveys Survey[]
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
cloudConfig Json? @map("cloud_config") // Langfuse Cloud, for zod schema see @/src/features/organizations/utils/cloudConfigSchema
|
||||
metadata Json?
|
||||
cloudBillingCycleAnchor DateTime? @default(now()) @map("cloud_billing_cycle_anchor")
|
||||
cloudBillingCycleUpdatedAt DateTime? @map("cloud_billing_cycle_updated_at")
|
||||
cloudCurrentCycleUsage Int? @map("cloud_current_cycle_usage")
|
||||
cloudFreeTierUsageThresholdState String? @map("cloud_free_tier_usage_threshold_state")
|
||||
aiFeaturesEnabled Boolean @default(false) @map("ai_features_enabled")
|
||||
organizationMemberships OrganizationMembership[]
|
||||
projects Project[]
|
||||
MembershipInvitation MembershipInvitation[]
|
||||
ApiKey ApiKey[]
|
||||
surveys Survey[]
|
||||
cloudSpendAlerts CloudSpendAlert[]
|
||||
|
||||
@@map("organizations")
|
||||
}
|
||||
@@ -853,6 +859,8 @@ model EvalTemplate {
|
||||
@@map("eval_templates")
|
||||
}
|
||||
|
||||
// We currently assume in the evalRouter that _all_ job_executions are for EVAL job_configs.
|
||||
// If we ever extend this, we need to adjust the filter condition there. ref.: fetchJobExecutionsByStatus.
|
||||
enum JobType {
|
||||
EVAL
|
||||
}
|
||||
@@ -927,6 +935,7 @@ model JobExecution {
|
||||
@@index([projectId, jobConfigurationId, jobInputTraceId])
|
||||
@@index([projectId, status])
|
||||
@@index([projectId, id])
|
||||
@@index([projectId, jobOutputScoreId])
|
||||
@@map("job_executions")
|
||||
}
|
||||
|
||||
@@ -1425,3 +1434,20 @@ enum SurveyName {
|
||||
|
||||
@@map("SurveyName")
|
||||
}
|
||||
|
||||
model CloudSpendAlert {
|
||||
id String @id @default(cuid())
|
||||
orgId String @map("org_id")
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
|
||||
title String // e.g., "Production Alert"
|
||||
threshold Decimal @map("threshold") // USD amount
|
||||
|
||||
triggeredAt DateTime? @map("triggered_at") // Last trigger timestamp
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@index([orgId])
|
||||
@@map("cloud_spend_alerts")
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
JobExecutionStatus,
|
||||
PrismaClient,
|
||||
type Project,
|
||||
ScoreConfigCategoryDomain,
|
||||
ScoreDataType,
|
||||
} from "../../src/index";
|
||||
import { getDisplaySecretKey, hashSecretKey, logger } from "../../src/server";
|
||||
@@ -24,17 +25,11 @@ import {
|
||||
} from "./utils/postgres-seed-constants";
|
||||
import {
|
||||
generateDatasetItemId,
|
||||
generateDatasetRunTraceId,
|
||||
generateEvalObservationId,
|
||||
generateEvalScoreId,
|
||||
generateEvalTraceId,
|
||||
} from "./utils/seed-helpers";
|
||||
|
||||
type ConfigCategory = {
|
||||
label: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
const options = {
|
||||
environment: { type: "string" },
|
||||
} as const;
|
||||
@@ -111,6 +106,9 @@ async function main() {
|
||||
},
|
||||
});
|
||||
|
||||
// Realistic support chat scenario
|
||||
await createSupportChatSession(project1);
|
||||
|
||||
await prisma.organizationMembership.upsert({
|
||||
where: {
|
||||
orgId_userId: {
|
||||
@@ -534,23 +532,13 @@ export async function createDatasets(
|
||||
const datasetItem = await prisma.datasetItem.upsert({
|
||||
where: {
|
||||
id_projectId: {
|
||||
id: generateDatasetItemId(
|
||||
datasetName,
|
||||
index,
|
||||
projectId,
|
||||
SEED_DATASETS.indexOf(data) || 0,
|
||||
),
|
||||
id: generateDatasetItemId(datasetName, index, projectId),
|
||||
projectId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
projectId,
|
||||
id: generateDatasetItemId(
|
||||
datasetName,
|
||||
index,
|
||||
projectId,
|
||||
SEED_DATASETS.indexOf(data) || 0,
|
||||
),
|
||||
id: generateDatasetItemId(datasetName, index, projectId),
|
||||
datasetId: dataset.id,
|
||||
sourceTraceId: sourceTraceId ?? null,
|
||||
sourceObservationId: null,
|
||||
@@ -564,17 +552,17 @@ export async function createDatasets(
|
||||
}
|
||||
|
||||
for (let datasetRunNumber = 0; datasetRunNumber < 3; datasetRunNumber++) {
|
||||
const datasetRun = await prisma.datasetRuns.upsert({
|
||||
await prisma.datasetRuns.upsert({
|
||||
where: {
|
||||
id_projectId: {
|
||||
id: `demo-dataset-run-${datasetRunNumber}-${projectId.slice(-8)}`,
|
||||
id: `demo-dataset-run-${datasetRunNumber}-${datasetName}-${projectId.slice(-8)}`,
|
||||
projectId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
projectId,
|
||||
id: `demo-dataset-run-${datasetRunNumber}-${projectId.slice(-8)}`,
|
||||
name: `demo-dataset-run-${datasetRunNumber}`,
|
||||
id: `demo-dataset-run-${datasetRunNumber}-${datasetName}-${projectId.slice(-8)}`,
|
||||
name: `demo-dataset-run-${datasetRunNumber}-${datasetName}`,
|
||||
description: Math.random() > 0.5 ? "Dataset run description" : "",
|
||||
datasetId: dataset.id,
|
||||
metadata: [
|
||||
@@ -587,25 +575,6 @@ export async function createDatasets(
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
for (let index = 0; index < datasetItemIds.length; index++) {
|
||||
await prisma.datasetRunItems.upsert({
|
||||
where: {
|
||||
id_projectId: {
|
||||
id: `${dataset.id}-${index}-${datasetRunNumber}`,
|
||||
projectId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
id: `${dataset.id}-${index}-${datasetRunNumber}`,
|
||||
projectId,
|
||||
datasetItemId: datasetItemIds[index],
|
||||
traceId: `${generateDatasetRunTraceId(datasetName, index, projectId, datasetRunNumber)}`,
|
||||
datasetRunId: datasetRun.id,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -753,6 +722,7 @@ async function generatePrompts(project: Project) {
|
||||
createdBy: version.createdBy,
|
||||
prompt: version.prompt,
|
||||
name: version.name,
|
||||
type: version.type ?? "text",
|
||||
config: version.config,
|
||||
version: version.version,
|
||||
labels: version.labels,
|
||||
@@ -774,7 +744,7 @@ async function generateConfigsForProject(projects: Project[]) {
|
||||
name: string;
|
||||
id: string;
|
||||
dataType: ScoreDataType;
|
||||
categories: ConfigCategory[] | null;
|
||||
categories: ScoreConfigCategoryDomain[] | null;
|
||||
}[]
|
||||
> = new Map();
|
||||
|
||||
@@ -801,12 +771,30 @@ async function createTraceSessions(project1: Project, project2: Project) {
|
||||
}
|
||||
}
|
||||
|
||||
async function createSupportChatSession(project: Project) {
|
||||
const sessionId = "support-chat-session";
|
||||
await prisma.traceSession.upsert({
|
||||
where: {
|
||||
id_projectId: {
|
||||
id: sessionId,
|
||||
projectId: project.id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
id: sessionId,
|
||||
projectId: project.id,
|
||||
environment: "default",
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
|
||||
async function generateConfigs(project: Project) {
|
||||
const configNameAndId: {
|
||||
name: string;
|
||||
id: string;
|
||||
dataType: ScoreDataType;
|
||||
categories: ConfigCategory[] | null;
|
||||
categories: ScoreConfigCategoryDomain[] | null;
|
||||
}[] = [];
|
||||
|
||||
const configs = [
|
||||
@@ -883,7 +871,7 @@ async function generateQueuesForProject(
|
||||
name: string;
|
||||
id: string;
|
||||
dataType: ScoreDataType;
|
||||
categories: ConfigCategory[] | null;
|
||||
categories: ScoreConfigCategoryDomain[] | null;
|
||||
}[]
|
||||
>,
|
||||
) {
|
||||
@@ -907,7 +895,7 @@ async function generateQueues(
|
||||
name: string;
|
||||
id: string;
|
||||
dataType: ScoreDataType;
|
||||
categories: ConfigCategory[] | null;
|
||||
categories: ScoreConfigCategoryDomain[] | null;
|
||||
}[],
|
||||
) {
|
||||
const queue = {
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
TraceRecordInsertType,
|
||||
DatasetRunItemRecordInsertType,
|
||||
createDatasetRunItem,
|
||||
createDatasetRunScore,
|
||||
} from "../../../src/server";
|
||||
|
||||
/**
|
||||
@@ -86,7 +87,6 @@ export class DataGenerator {
|
||||
input.runNumber || 0,
|
||||
);
|
||||
|
||||
// TODO: there are too many dataset run items in the postgres database?
|
||||
return createDatasetRunItem({
|
||||
id: datasetRunItemId,
|
||||
project_id: projectId,
|
||||
@@ -97,8 +97,8 @@ export class DataGenerator {
|
||||
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_id: `demo-dataset-run-${input.runNumber}-${input.datasetName}-${projectId.slice(-8)}`,
|
||||
dataset_run_name: `demo-dataset-run-${input.runNumber}-${input.datasetName}`,
|
||||
dataset_run_created_at: input.runCreatedAt,
|
||||
dataset_run_description:
|
||||
(input.runNumber || 0) % 2 === 0 ? "Dataset run description" : "",
|
||||
@@ -107,10 +107,9 @@ export class DataGenerator {
|
||||
input.datasetName,
|
||||
input.itemIndex,
|
||||
projectId,
|
||||
input.runNumber || 0,
|
||||
),
|
||||
dataset_item_input: input.item.input,
|
||||
dataset_item_expected_output: input.item.output,
|
||||
dataset_item_expected_output: input.item.expectedOutput,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -217,6 +216,59 @@ export class DataGenerator {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates scores for dataset experiment scores with variable values.
|
||||
* Use for: Dataset experiments requiring detailed score tracking.
|
||||
*/
|
||||
generateDatasetScore(
|
||||
trace: TraceRecordInsertType,
|
||||
input: DatasetItemInput,
|
||||
projectId: string,
|
||||
scoreNames: string[],
|
||||
): ScoreRecordInsertType {
|
||||
const scoreId = `score-dataset-${input.datasetName}-${input.itemIndex}-${input.runNumber}-${projectId.slice(-8)}`;
|
||||
|
||||
return createTraceScore({
|
||||
id: scoreId,
|
||||
trace_id: trace.id,
|
||||
project_id: projectId,
|
||||
name: this.randomElement(scoreNames),
|
||||
value: Math.random() * 100,
|
||||
string_value: undefined,
|
||||
data_type: "NUMERIC",
|
||||
source: "API",
|
||||
environment: "langfuse-prompt-experiments",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates scores for dataset experiment traces with variable values.
|
||||
* Use for: Dataset experiments requiring detailed score tracking.
|
||||
*/
|
||||
generateDatasetRunScore(
|
||||
runId: string,
|
||||
input: {
|
||||
datasetName: string;
|
||||
runNumber: number;
|
||||
},
|
||||
projectId: string,
|
||||
scoreNames: string[],
|
||||
): ScoreRecordInsertType {
|
||||
const scoreId = `score-dataset-run-${input.datasetName}-${input.runNumber}-${projectId.slice(-8)}`;
|
||||
|
||||
return createDatasetRunScore({
|
||||
id: scoreId,
|
||||
project_id: projectId,
|
||||
dataset_run_id: runId,
|
||||
name: this.randomElement(scoreNames),
|
||||
value: Math.random() * 100,
|
||||
string_value: undefined,
|
||||
data_type: "NUMERIC",
|
||||
source: "API",
|
||||
environment: "langfuse-prompt-experiments",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates large-scale synthetic traces for performance testing.
|
||||
* Use for: Load testing, dashboard demos, realistic usage simulation.
|
||||
@@ -900,6 +952,310 @@ export class DataGenerator {
|
||||
: JSON.stringify(this.fileContent.chatMlJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates realistic support chat session data with conversational flow.
|
||||
* Use for: Demonstrating session-based conversations with tool calls and scoring.
|
||||
*/
|
||||
generateSupportChatSessionData(projectId: string): {
|
||||
traces: TraceRecordInsertType[];
|
||||
observations: ObservationRecordInsertType[];
|
||||
scores: ScoreRecordInsertType[];
|
||||
} {
|
||||
// Domain: Art museum membership support (non-tech)
|
||||
const dialogues = [
|
||||
{
|
||||
user: "Hi—my family membership at the City Art Museum was charged twice this month.",
|
||||
assistant:
|
||||
"Sorry about that. I can help. Could you share your membership ID and the last 4 digits of the card?",
|
||||
},
|
||||
{
|
||||
user: "Membership ID is CAM-48291, card ending 4417.",
|
||||
assistant: "Thanks. I'll check your billing records now.",
|
||||
tool: {
|
||||
name: "billing.lookup",
|
||||
input: { membershipId: "CAM-48291", last4: "4417" },
|
||||
output: {
|
||||
duplicateCharge: true,
|
||||
amounts: [95.0, 95.0],
|
||||
date: "2025-08-28",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
user: "Okay.",
|
||||
assistant:
|
||||
"I found a duplicate charge on 2025-08-28 for $95. I can issue a refund to the original payment method. Shall I proceed?",
|
||||
},
|
||||
{
|
||||
user: "Yes, please issue the refund.",
|
||||
assistant: "Processing your refund now. One moment.",
|
||||
tool: {
|
||||
name: "billing.refund",
|
||||
input: { membershipId: "CAM-48291", amount: 95.0 },
|
||||
output: { status: "success", refundId: "RFND-20931" },
|
||||
},
|
||||
},
|
||||
{
|
||||
user: "Thank you!",
|
||||
assistant:
|
||||
"Refund RFND-20931 has been issued. You'll see it on your statement within 3–5 business days.",
|
||||
},
|
||||
{
|
||||
user: "No, that's all. Appreciate the quick help!",
|
||||
assistant: "Happy to help. Enjoy your next visit to the museum!",
|
||||
},
|
||||
// a couple more lightweight turns for scrolling realism
|
||||
{
|
||||
user: "Oh, and do members get early access to exhibitions?",
|
||||
assistant:
|
||||
"Yes—members get a 48-hour early booking window and a preview evening invite.",
|
||||
},
|
||||
{
|
||||
user: "Perfect.",
|
||||
assistant: "You're all set. Have a great day!",
|
||||
},
|
||||
];
|
||||
|
||||
const now = Date.now();
|
||||
const traces: TraceRecordInsertType[] = dialogues.map((d, index) => ({
|
||||
id: `support-chat-${index}-${projectId.slice(-8)}`,
|
||||
timestamp: now + index * 1000,
|
||||
name: "SupportChatSession",
|
||||
user_id: null,
|
||||
metadata: { scenario: "support-chat" },
|
||||
release: null,
|
||||
version: null,
|
||||
project_id: projectId,
|
||||
environment: "default",
|
||||
public: false,
|
||||
bookmarked: false,
|
||||
tags: ["support", "chat", "session"],
|
||||
input: JSON.stringify(
|
||||
d.tool
|
||||
? {
|
||||
messages: [
|
||||
{ role: "user", content: d.user },
|
||||
{ role: "assistant", content: d.assistant },
|
||||
{
|
||||
role: "tool",
|
||||
name: d.tool.name,
|
||||
content: d.tool.output,
|
||||
},
|
||||
],
|
||||
}
|
||||
: { messages: [{ role: "user", content: d.user }] },
|
||||
),
|
||||
output: JSON.stringify({ role: "assistant", content: d.assistant }),
|
||||
session_id: "support-chat-session",
|
||||
created_at: now + index * 1000,
|
||||
updated_at: now + index * 1000 + 500,
|
||||
event_ts: now + index * 1000,
|
||||
is_deleted: 0,
|
||||
}));
|
||||
|
||||
// Create one GENERATION observation per trace
|
||||
const observations: ObservationRecordInsertType[] = dialogues
|
||||
.map((d, index) => {
|
||||
const start = now + index * 1000 + 50;
|
||||
const end = start + 400 + Math.floor(Math.random() * 400);
|
||||
const inputTokens = 80 + Math.floor(Math.random() * 60);
|
||||
const outputTokens = 60 + Math.floor(Math.random() * 60);
|
||||
const totalTokens = inputTokens + outputTokens;
|
||||
|
||||
const baseGen: ObservationRecordInsertType = {
|
||||
id: `support-chat-${index}-${projectId.slice(-8)}-gen`,
|
||||
trace_id: `support-chat-${index}-${projectId.slice(-8)}`,
|
||||
project_id: projectId,
|
||||
type: "GENERATION",
|
||||
parent_observation_id: null,
|
||||
environment: "default",
|
||||
start_time: start,
|
||||
end_time: end,
|
||||
name: "llm-generation",
|
||||
metadata: {},
|
||||
level: "DEFAULT",
|
||||
status_message: null,
|
||||
version: null,
|
||||
input: JSON.stringify({
|
||||
messages: [
|
||||
{ role: "user", content: d.user },
|
||||
d.tool
|
||||
? {
|
||||
role: "tool",
|
||||
name: d.tool.name,
|
||||
content: d.tool.output,
|
||||
}
|
||||
: undefined,
|
||||
].filter(Boolean),
|
||||
}),
|
||||
output: JSON.stringify({ role: "assistant", content: d.assistant }),
|
||||
provided_model_name: "gpt-4o",
|
||||
internal_model_id: null,
|
||||
model_parameters: JSON.stringify({ temperature: 0.2 }),
|
||||
provided_usage_details: {
|
||||
input: inputTokens,
|
||||
output: outputTokens,
|
||||
total: totalTokens,
|
||||
},
|
||||
usage_details: {
|
||||
input: inputTokens,
|
||||
output: outputTokens,
|
||||
total: totalTokens,
|
||||
},
|
||||
provided_cost_details: {
|
||||
input: Math.round(inputTokens * 2) / 1_000_000,
|
||||
output: Math.round(outputTokens * 3) / 1_000_000,
|
||||
total: Math.round(totalTokens * 5) / 1_000_000,
|
||||
},
|
||||
cost_details: {
|
||||
input: Math.round(inputTokens * 2) / 1_000_000,
|
||||
output: Math.round(outputTokens * 3) / 1_000_000,
|
||||
total: Math.round(totalTokens * 5) / 1_000_000,
|
||||
},
|
||||
total_cost: Math.round(totalTokens * 5) / 1_000_000,
|
||||
completion_start_time: start + 120,
|
||||
prompt_id: null,
|
||||
prompt_name: null,
|
||||
prompt_version: null,
|
||||
created_at: start,
|
||||
updated_at: end,
|
||||
event_ts: start,
|
||||
is_deleted: 0,
|
||||
};
|
||||
|
||||
if (!d.tool) return [baseGen];
|
||||
|
||||
const toolObs: ObservationRecordInsertType = {
|
||||
id: `support-chat-${index}-${projectId.slice(-8)}-tool`,
|
||||
trace_id: `support-chat-${index}-${projectId.slice(-8)}`,
|
||||
project_id: projectId,
|
||||
type: "TOOL",
|
||||
parent_observation_id: null,
|
||||
environment: "default",
|
||||
start_time: start - 40,
|
||||
end_time: start - 5,
|
||||
name: d.tool.name,
|
||||
metadata: {},
|
||||
level: "DEFAULT",
|
||||
status_message: null,
|
||||
version: null,
|
||||
input: JSON.stringify(d.tool.input),
|
||||
output: JSON.stringify(d.tool.output),
|
||||
provided_model_name: null,
|
||||
internal_model_id: null,
|
||||
model_parameters: null,
|
||||
provided_usage_details: {},
|
||||
usage_details: {},
|
||||
provided_cost_details: {},
|
||||
cost_details: {},
|
||||
total_cost: null,
|
||||
completion_start_time: null,
|
||||
prompt_id: null,
|
||||
prompt_name: null,
|
||||
prompt_version: null,
|
||||
created_at: start - 40,
|
||||
updated_at: start - 5,
|
||||
event_ts: start - 40,
|
||||
is_deleted: 0,
|
||||
};
|
||||
|
||||
return [toolObs, baseGen];
|
||||
})
|
||||
.flat();
|
||||
|
||||
// Create a couple of scores per trace
|
||||
const scores: ScoreRecordInsertType[] = dialogues
|
||||
.map((_, index) => {
|
||||
const baseTs = now + index * 1000 + 600;
|
||||
const helpfulness: ScoreRecordInsertType = {
|
||||
id: `support-chat-${index}-${projectId.slice(-8)}-score-helpfulness`,
|
||||
project_id: projectId,
|
||||
trace_id: `support-chat-${index}-${projectId.slice(-8)}`,
|
||||
session_id: null,
|
||||
dataset_run_id: null,
|
||||
observation_id: null,
|
||||
environment: "default",
|
||||
name: "helpfulness",
|
||||
value: 70 + Math.random() * 25,
|
||||
source: "API",
|
||||
comment: "Heuristic helpfulness score",
|
||||
metadata: {},
|
||||
author_user_id: null,
|
||||
config_id: null,
|
||||
data_type: "NUMERIC",
|
||||
string_value: null,
|
||||
queue_id: null,
|
||||
created_at: baseTs,
|
||||
updated_at: baseTs,
|
||||
timestamp: baseTs,
|
||||
event_ts: baseTs,
|
||||
is_deleted: 0,
|
||||
};
|
||||
|
||||
const safeVal = Math.random() > 0.1 ? 1 : 0;
|
||||
const safety: ScoreRecordInsertType = {
|
||||
id: `support-chat-${index}-${projectId.slice(-8)}-score-safety`,
|
||||
project_id: projectId,
|
||||
trace_id: `support-chat-${index}-${projectId.slice(-8)}`,
|
||||
session_id: null,
|
||||
dataset_run_id: null,
|
||||
observation_id: null,
|
||||
environment: "default",
|
||||
name: "safe",
|
||||
value: safeVal,
|
||||
source: "API",
|
||||
comment: "Content safety",
|
||||
metadata: {},
|
||||
author_user_id: null,
|
||||
config_id: null,
|
||||
data_type: "BOOLEAN",
|
||||
string_value: safeVal === 1 ? "true" : "false",
|
||||
queue_id: null,
|
||||
created_at: baseTs + 10,
|
||||
updated_at: baseTs + 10,
|
||||
timestamp: baseTs + 10,
|
||||
event_ts: baseTs + 10,
|
||||
is_deleted: 0,
|
||||
};
|
||||
|
||||
// Optional: resolution score on last turn
|
||||
const isFinal = index === dialogues.length - 1;
|
||||
const resolved: ScoreRecordInsertType | null = isFinal
|
||||
? {
|
||||
id: `support-chat-${index}-${projectId.slice(-8)}-score-resolved`,
|
||||
project_id: projectId,
|
||||
trace_id: `support-chat-${index}-${projectId.slice(-8)}`,
|
||||
session_id: null,
|
||||
dataset_run_id: null,
|
||||
observation_id: null,
|
||||
environment: "default",
|
||||
name: "resolved",
|
||||
value: 1,
|
||||
source: "API",
|
||||
comment: "Conversation resolved",
|
||||
metadata: {},
|
||||
author_user_id: null,
|
||||
config_id: null,
|
||||
data_type: "BOOLEAN",
|
||||
string_value: "true",
|
||||
queue_id: null,
|
||||
created_at: baseTs + 20,
|
||||
updated_at: baseTs + 20,
|
||||
timestamp: baseTs + 20,
|
||||
event_ts: baseTs + 20,
|
||||
is_deleted: 0,
|
||||
}
|
||||
: null;
|
||||
|
||||
return [helpfulness, safety, resolved].filter(
|
||||
Boolean,
|
||||
) as ScoreRecordInsertType[];
|
||||
})
|
||||
.flat();
|
||||
|
||||
return { traces, observations, scores };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates exactly one score per evaluation trace with prefixed IDs.
|
||||
* Use for: Evaluation traces that need score validation, evaluator testing.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Framework Traces
|
||||
|
||||
This folder contains real traces produced through framework instrumentation.
|
||||
Most of them stem from here: https://langfuse.com/integrations/frameworks/agno-agents and so on.
|
||||
|
||||
## How to add further traces
|
||||
1. Generate a trace
|
||||
2. Download the trace from the UI using the download button. This **excludes** the `input`/`output`/`metadata` fields of the observations
|
||||
3. In the trace, click `Log View (Beta)` and switch to `JSON` format. Click the copy all button and save this to a file in this folder.
|
||||
4. Run the script `npx ts-node merge-observations.ts trace-file.json observations.json trace-merged.json`
|
||||
5. Leave the `merged` file in this folder. Name it with the date of the trace as displayed in the UI.
|
||||
6. ???
|
||||
7. Profit
|
||||
|
||||
## How to use the trace in the UI
|
||||
|
||||
All trace ids are like `framework-frameworkName-traceId`, so search for framework in the trace table.
|
||||
We don't rewrite the time, so you have to filter for All Time most likely.
|
||||
Also, you can filter for `source: "framework-trace"` on the trace to find them.
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"trace": {
|
||||
"id": "1b72c51fabed12ae7df83bfd4a09f545",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"name": "autogen-chat-trace",
|
||||
"timestamp": "2025-06-06T11:31:33.965Z",
|
||||
"environment": "default",
|
||||
"tags": [
|
||||
"autogen",
|
||||
"dev"
|
||||
],
|
||||
"bookmarked": false,
|
||||
"release": null,
|
||||
"version": "1.0.0",
|
||||
"userId": "user_123",
|
||||
"sessionId": "session_abc",
|
||||
"public": true,
|
||||
"input": "\"Say 'Hello World!'\"",
|
||||
"output": "\"Hello World!\"",
|
||||
"metadata": "{\"email\":\"user@langfuse.com\",\"resourceAttributes\":{\"telemetry.sdk.language\":\"python\",\"telemetry.sdk.name\":\"opentelemetry\",\"telemetry.sdk.version\":\"1.33.1\",\"service.name\":\"unknown_service\"},\"scope\":{\"name\":\"langfuse-sdk\",\"version\":\"3.0.0\",\"attributes\":{\"public_key\":\"pk-lf-5855d85e-3943-497e-bd10-f50ad414bcba\"}}}",
|
||||
"createdAt": "2025-06-06T11:31:35.000Z",
|
||||
"updatedAt": "2025-06-06T11:31:35.337Z",
|
||||
"scores": [],
|
||||
"latency": 0.645
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"id": "95f4f51edeb603d9",
|
||||
"traceId": "1b72c51fabed12ae7df83bfd4a09f545",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2025-06-06T11:31:33.965Z",
|
||||
"endTime": "2025-06-06T11:31:34.610Z",
|
||||
"name": "autogen-chat-trace",
|
||||
"metadata": {
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.33.1",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "langfuse-sdk",
|
||||
"version": "3.0.0",
|
||||
"attributes": {
|
||||
"public_key": "pk-lf-5855d85e-3943-497e-bd10-f50ad414bcba"
|
||||
}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "1.0.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-06-06T11:31:35.295Z",
|
||||
"updatedAt": "2025-06-06T11:31:35.337Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 645,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "83517f9062f5ada4",
|
||||
"traceId": "1b72c51fabed12ae7df83bfd4a09f545",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "95f4f51edeb603d9",
|
||||
"startTime": "2025-06-06T11:31:33.978Z",
|
||||
"endTime": "2025-06-06T11:31:34.606Z",
|
||||
"name": "chat gpt-4o",
|
||||
"metadata": {
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.33.1",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "langfuse-sdk",
|
||||
"version": "3.0.0",
|
||||
"attributes": {
|
||||
"public_key": "pk-lf-5855d85e-3943-497e-bd10-f50ad414bcba"
|
||||
}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {
|
||||
"seed": "",
|
||||
"frequency_penalty": 0,
|
||||
"max_tokens": -1,
|
||||
"presence_penalty": 0,
|
||||
"stop_sequences": "{\"arrayValue\":{}}",
|
||||
"temperature": 1,
|
||||
"top_p": 1,
|
||||
"service_tier": "auto",
|
||||
"user": "",
|
||||
"is_stream": false
|
||||
},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-06-06T11:31:35.090Z",
|
||||
"updatedAt": "2025-06-06T11:31:35.153Z",
|
||||
"usageDetails": {
|
||||
"input": 41,
|
||||
"output": 3,
|
||||
"total": 44
|
||||
},
|
||||
"costDetails": {
|
||||
"total": 0.000025
|
||||
},
|
||||
"providedCostDetails": {
|
||||
"total": 0.000025
|
||||
},
|
||||
"model": "gpt-4o",
|
||||
"internalModelId": "b9854a5c92dc496b997d99d20",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 628,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0.000025,
|
||||
"inputUsage": 41,
|
||||
"outputUsage": 3,
|
||||
"totalUsage": 44,
|
||||
"input": "system: You are a helpful AI assistant. Solve tasks using your tools. Reply with TERMINATE when the task has been completed.\nuser: Say 'Hello World!'",
|
||||
"output": "Hello World!"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,203 @@
|
||||
import { readFileSync, readdirSync } from "fs";
|
||||
import path from "path";
|
||||
import {
|
||||
createTrace,
|
||||
createObservation,
|
||||
logger,
|
||||
type TraceRecordInsertType,
|
||||
type ObservationRecordInsertType,
|
||||
type ScoreRecordInsertType,
|
||||
} from "../../../../src/server";
|
||||
|
||||
/**
|
||||
* Loads framework traces from JSON files and converts them to ClickHouse insert types.
|
||||
* Expected JSON structure:
|
||||
* {
|
||||
* trace: { trace data without observations },
|
||||
* observations: [{ individual observation with id and input/output/metadata and all the other stuff}]
|
||||
* }
|
||||
*/
|
||||
export class FrameworkTraceLoader {
|
||||
private frameworkTracesDir: string;
|
||||
|
||||
constructor() {
|
||||
this.frameworkTracesDir = __dirname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and adapt all framework traces for a project.
|
||||
*/
|
||||
loadTracesForProject(projectId: string): {
|
||||
traces: TraceRecordInsertType[];
|
||||
observations: ObservationRecordInsertType[];
|
||||
scores: ScoreRecordInsertType[];
|
||||
} {
|
||||
const traces: TraceRecordInsertType[] = [];
|
||||
const observations: ObservationRecordInsertType[] = [];
|
||||
const scores: ScoreRecordInsertType[] = [];
|
||||
|
||||
const files = readdirSync(this.frameworkTracesDir).filter(
|
||||
(file) => file.endsWith(".json") && file !== "package.json",
|
||||
);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(this.frameworkTracesDir, file);
|
||||
const content = readFileSync(filePath, "utf-8");
|
||||
const data = JSON.parse(content);
|
||||
|
||||
const frameworkName = file.replace(".json", "");
|
||||
const adapted = this.adaptTrace(data, projectId, frameworkName);
|
||||
|
||||
traces.push(adapted.trace);
|
||||
observations.push(...adapted.observations);
|
||||
scores.push(...adapted.scores);
|
||||
|
||||
logger.info(
|
||||
`Loaded 1 trace with ${adapted.observations.length} observations from ${frameworkName}`,
|
||||
);
|
||||
}
|
||||
|
||||
return { traces, observations, scores };
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt a single framework trace to a specific project.
|
||||
*/
|
||||
private adaptTrace(
|
||||
data: any,
|
||||
projectId: string,
|
||||
frameworkName: string,
|
||||
): {
|
||||
trace: TraceRecordInsertType;
|
||||
observations: ObservationRecordInsertType[];
|
||||
scores: ScoreRecordInsertType[];
|
||||
} {
|
||||
const rawTrace = data.trace;
|
||||
const rawObservations = data.observations || [];
|
||||
|
||||
const originalTraceId = rawTrace.id;
|
||||
const newTraceId = originalTraceId;
|
||||
// we just change the name for now.
|
||||
// const newTraceId = `framework-${frameworkName}-${originalTraceId}-${projectId.slice(-8)}`;
|
||||
const newName = `framework-${frameworkName}-${rawTrace.name}`;
|
||||
|
||||
// Parse trace metadata if it's a string
|
||||
let metadata = rawTrace.metadata;
|
||||
if (typeof metadata === "string") {
|
||||
metadata = JSON.parse(metadata);
|
||||
}
|
||||
|
||||
// Create trace
|
||||
const trace = createTrace({
|
||||
id: newTraceId,
|
||||
project_id: projectId,
|
||||
name: newName,
|
||||
timestamp: new Date(rawTrace.timestamp).getTime(),
|
||||
input: rawTrace.input,
|
||||
output: rawTrace.output,
|
||||
user_id: rawTrace.userId,
|
||||
session_id: rawTrace.sessionId,
|
||||
environment: rawTrace.environment,
|
||||
metadata: {
|
||||
...metadata,
|
||||
// metadata to filter for those kinda traces
|
||||
source: "framework-trace",
|
||||
framework: frameworkName,
|
||||
},
|
||||
release: rawTrace.release,
|
||||
version: rawTrace.version,
|
||||
public: rawTrace.public,
|
||||
bookmarked: rawTrace.bookmarked,
|
||||
tags: rawTrace.tags,
|
||||
});
|
||||
|
||||
// Map observation IDs (old -> new)
|
||||
const obsIdMap = new Map<string, string>();
|
||||
for (const obs of rawObservations) {
|
||||
const newObsId = `framework-${frameworkName}-${obs.id}-${projectId.slice(-8)}`;
|
||||
obsIdMap.set(obs.id, newObsId);
|
||||
}
|
||||
|
||||
// Create observations
|
||||
const observations: ObservationRecordInsertType[] = [];
|
||||
for (const obs of rawObservations) {
|
||||
const newObsId = obsIdMap.get(obs.id)!;
|
||||
|
||||
// Map parent observation ID
|
||||
const parentObservationId = obs.parentObservationId
|
||||
? obsIdMap.get(obs.parentObservationId) || null
|
||||
: null;
|
||||
|
||||
// Convert input/output to strings, preserving null
|
||||
const input =
|
||||
obs.input === undefined || obs.input === null
|
||||
? obs.input
|
||||
: JSON.stringify(obs.input);
|
||||
const output =
|
||||
obs.output === undefined || obs.output === null
|
||||
? obs.output
|
||||
: JSON.stringify(obs.output);
|
||||
|
||||
// Parse metadata if it's a string
|
||||
let obsMetadata = obs.metadata;
|
||||
if (typeof obsMetadata === "string") {
|
||||
obsMetadata = JSON.parse(obsMetadata);
|
||||
}
|
||||
|
||||
// Use usage/cost details from JSON if present, otherwise reconstruct from flat fields
|
||||
const usageDetails =
|
||||
obs.usageDetails && Object.keys(obs.usageDetails).length > 0
|
||||
? obs.usageDetails
|
||||
: obs.totalUsage > 0
|
||||
? {
|
||||
input: obs.inputUsage,
|
||||
output: obs.outputUsage,
|
||||
total: obs.totalUsage,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const costDetails =
|
||||
obs.costDetails && Object.keys(obs.costDetails).length > 0
|
||||
? obs.costDetails
|
||||
: obs.totalCost > 0
|
||||
? {
|
||||
input: (obs.totalCost * obs.inputUsage) / obs.totalUsage,
|
||||
output: (obs.totalCost * obs.outputUsage) / obs.totalUsage,
|
||||
total: obs.totalCost,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
observations.push(
|
||||
createObservation({
|
||||
id: newObsId,
|
||||
trace_id: newTraceId,
|
||||
project_id: projectId,
|
||||
type: obs.type,
|
||||
parent_observation_id: parentObservationId,
|
||||
start_time: new Date(obs.startTime).getTime(),
|
||||
end_time: obs.endTime ? new Date(obs.endTime).getTime() : undefined,
|
||||
name: obs.name,
|
||||
metadata: obsMetadata,
|
||||
level: obs.level,
|
||||
status_message: obs.statusMessage,
|
||||
input,
|
||||
output,
|
||||
provided_model_name: obs.model,
|
||||
model_parameters: obs.modelParameters
|
||||
? JSON.stringify(obs.modelParameters)
|
||||
: undefined,
|
||||
prompt_name: obs.promptName,
|
||||
prompt_version: obs.promptVersion,
|
||||
usage_details: usageDetails,
|
||||
provided_usage_details: obs.providedUsageDetails,
|
||||
cost_details: costDetails,
|
||||
provided_cost_details: obs.providedCostDetails,
|
||||
total_cost: obs.totalCost,
|
||||
environment: rawTrace.environment,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return { trace, observations, scores: [] };
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,863 @@
|
||||
{
|
||||
"trace": {
|
||||
"id": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"name": "agent.f9f77d1a-437f-4ba6-890e-b592a50cbd21",
|
||||
"timestamp": "2025-08-26T19:40:59.145Z",
|
||||
"environment": "default",
|
||||
"tags": [],
|
||||
"bookmarked": false,
|
||||
"release": null,
|
||||
"version": "0.3.0",
|
||||
"userId": null,
|
||||
"sessionId": null,
|
||||
"public": true,
|
||||
"input": null,
|
||||
"output": null,
|
||||
"metadata": "{\"attributes\":{\"gen_ai.request.model\":\"gpt-4o\",\"gen_ai.system\":\"openai\",\"gen_ai.agent.id\":\"f9f77d1a-437f-4ba6-890e-b592a50cbd21\",\"gen_ai.operation.name\":\"create_agent\"},\"resourceAttributes\":{\"os.arch\":\"aarch64\",\"os.type\":\"Mac OS X\",\"os.version\":\"15.4.1\",\"service.instance.time\":\"2025-08-26T19:40:59.130398Z\",\"service.name\":\"ai.koog\",\"service.version\":\"0.3.0\"},\"scope\":{\"name\":\"ai.koog\",\"version\":\"0.3.0\",\"attributes\":{}}}",
|
||||
"createdAt": "2025-08-26T19:40:59.145Z",
|
||||
"updatedAt": "2025-08-26T19:41:05.434Z",
|
||||
"scores": [],
|
||||
"latency": 5.843
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"id": "1b7200380e3316e7",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "AGENT",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2025-08-26T19:40:59.145Z",
|
||||
"endTime": "2025-08-26T19:41:04.988Z",
|
||||
"name": "agent.f9f77d1a-437f-4ba6-890e-b592a50cbd21",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"gen_ai.request.model": "gpt-4o",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.agent.id": "f9f77d1a-437f-4ba6-890e-b592a50cbd21",
|
||||
"gen_ai.operation.name": "create_agent"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": {},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:05.401Z",
|
||||
"updatedAt": "2025-08-26T19:41:05.420Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": "gpt-4o",
|
||||
"internalModelId": "b9854a5c92dc496b997d99d20",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 5843,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "bb189bc3771ff83d",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "AGENT",
|
||||
"environment": "default",
|
||||
"parentObservationId": "1b7200380e3316e7",
|
||||
"startTime": "2025-08-26T19:40:59.150Z",
|
||||
"endTime": "2025-08-26T19:41:04.988Z",
|
||||
"name": "run.6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"koog.agent.strategy.name": "single_run_sequential",
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.agent.id": "f9f77d1a-437f-4ba6-890e-b592a50cbd21",
|
||||
"gen_ai.operation.name": "invoke_agent"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": {},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:05.330Z",
|
||||
"updatedAt": "2025-08-26T19:41:05.354Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 5838,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "7578374e12a7faa5",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "bb189bc3771ff83d",
|
||||
"startTime": "2025-08-26T19:41:04.984Z",
|
||||
"endTime": "2025-08-26T19:41:04.985Z",
|
||||
"name": "node.__finish__",
|
||||
"metadata": {
|
||||
"langgraph_node": "__finish__",
|
||||
"langgraph_step": 4,
|
||||
"attributes": {
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"koog.node.name": "__finish__",
|
||||
"langfuse.observation.metadata.langgraph_node": "__finish__",
|
||||
"langfuse.observation.metadata.langgraph_step": "4"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:05.313Z",
|
||||
"updatedAt": "2025-08-26T19:41:05.340Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "e44e72cf2d221781",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "6021df46275ad2d8",
|
||||
"startTime": "2025-08-26T19:41:03.041Z",
|
||||
"endTime": "2025-08-26T19:41:04.983Z",
|
||||
"name": "llm.chat",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"gen_ai.prompt.4.content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "ByeTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_dOeEHJDI0liHrCdFWTpkTsuO",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"gen_ai.prompt.5.role": "tool",
|
||||
"gen_ai.completion.0.content": "Hello my darling Bob! And, bye my dear Bob!",
|
||||
"gen_ai.request.temperature": "1",
|
||||
"gen_ai.prompt.3.role": "tool",
|
||||
"gen_ai.prompt.0.role": "system",
|
||||
"gen_ai.prompt.5.content": "Bye my dear Bob!",
|
||||
"gen_ai.completion.0.role": "assistant",
|
||||
"gen_ai.prompt.4.role": "tool",
|
||||
"gen_ai.request.model": "gpt-4o",
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"gen_ai.prompt.0.content": "You are a nice and polite assistant.",
|
||||
"gen_ai.prompt.1.role": "user",
|
||||
"gen_ai.response.finish_reasons": [
|
||||
"stop"
|
||||
],
|
||||
"gen_ai.prompt.1.content": "Greet and say goodbye to the user in the same message. Use provided tools to generate a proper greeting. The user name is Bob.",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.prompt.2.content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "HelloTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_n2HaWg9KFP0BZDqXv6G4b3nJ",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"gen_ai.operation.name": "chat",
|
||||
"gen_ai.prompt.2.role": "tool",
|
||||
"gen_ai.prompt.3.content": "Hello my darling Bob!"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": {
|
||||
"temperature": 1
|
||||
},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:05.314Z",
|
||||
"updatedAt": "2025-08-26T19:41:05.333Z",
|
||||
"usageDetails": {
|
||||
"input": 168,
|
||||
"output": 19,
|
||||
"total": 187
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0.00042,
|
||||
"output": 0.00019,
|
||||
"total": 0.00061
|
||||
},
|
||||
"providedCostDetails": {},
|
||||
"model": "gpt-4o",
|
||||
"internalModelId": "b9854a5c92dc496b997d99d20",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1942,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0.00042,
|
||||
"outputCost": 0.00019,
|
||||
"totalCost": 0.00061,
|
||||
"inputUsage": 168,
|
||||
"outputUsage": 19,
|
||||
"totalUsage": 187,
|
||||
"input": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a nice and polite assistant."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Greet and say goodbye to the user in the same message. Use provided tools to generate a proper greeting. The user name is Bob."
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "HelloTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_n2HaWg9KFP0BZDqXv6G4b3nJ",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"role": "tool"
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"content": "Hello my darling Bob!"
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "ByeTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_dOeEHJDI0liHrCdFWTpkTsuO",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"role": "tool"
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"content": "Bye my dear Bob!"
|
||||
}
|
||||
],
|
||||
"output": [
|
||||
{
|
||||
"content": "Hello my darling Bob! And, bye my dear Bob!",
|
||||
"role": "assistant"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6021df46275ad2d8",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "bb189bc3771ff83d",
|
||||
"startTime": "2025-08-26T19:41:03.038Z",
|
||||
"endTime": "2025-08-26T19:41:04.984Z",
|
||||
"name": "node.nodeSendToolResult",
|
||||
"metadata": {
|
||||
"langgraph_node": "nodeSendToolResult",
|
||||
"langgraph_step": 3,
|
||||
"attributes": {
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"koog.node.name": "nodeSendToolResult",
|
||||
"langfuse.observation.metadata.langgraph_node": "nodeSendToolResult",
|
||||
"langfuse.observation.metadata.langgraph_step": "3"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:05.305Z",
|
||||
"updatedAt": "2025-08-26T19:41:05.324Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1946,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "33188493784a060d",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "5555afb6655e4d44",
|
||||
"startTime": "2025-08-26T19:41:02.023Z",
|
||||
"endTime": "2025-08-26T19:41:03.037Z",
|
||||
"name": "tool.ByeTool",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"input.value": {
|
||||
"name": "Bob"
|
||||
},
|
||||
"output.value": "Bye my dear Bob!",
|
||||
"gen_ai.tool.description": "A tool that says bye to the user",
|
||||
"gen_ai.tool.call.id": "call_dOeEHJDI0liHrCdFWTpkTsuO",
|
||||
"gen_ai.tool.name": "ByeTool"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:03.511Z",
|
||||
"updatedAt": "2025-08-26T19:41:03.531Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1014,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": {
|
||||
"name": "Bob"
|
||||
},
|
||||
"output": "Bye my dear Bob!"
|
||||
},
|
||||
{
|
||||
"id": "66f269083a2fff81",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "5555afb6655e4d44",
|
||||
"startTime": "2025-08-26T19:41:02.023Z",
|
||||
"endTime": "2025-08-26T19:41:03.034Z",
|
||||
"name": "tool.HelloTool",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"input.value": {
|
||||
"name": "Bob"
|
||||
},
|
||||
"output.value": "Hello my darling Bob!",
|
||||
"gen_ai.tool.description": "A tool greets the user",
|
||||
"gen_ai.tool.call.id": "call_n2HaWg9KFP0BZDqXv6G4b3nJ",
|
||||
"gen_ai.tool.name": "HelloTool"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:03.431Z",
|
||||
"updatedAt": "2025-08-26T19:41:03.479Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1011,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": {
|
||||
"name": "Bob"
|
||||
},
|
||||
"output": "Hello my darling Bob!"
|
||||
},
|
||||
{
|
||||
"id": "5555afb6655e4d44",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "bb189bc3771ff83d",
|
||||
"startTime": "2025-08-26T19:41:02.017Z",
|
||||
"endTime": "2025-08-26T19:41:03.038Z",
|
||||
"name": "node.nodeExecuteTool",
|
||||
"metadata": {
|
||||
"langgraph_node": "nodeExecuteTool",
|
||||
"langgraph_step": 2,
|
||||
"attributes": {
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"koog.node.name": "nodeExecuteTool",
|
||||
"langfuse.observation.metadata.langgraph_node": "nodeExecuteTool",
|
||||
"langfuse.observation.metadata.langgraph_step": "2"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:03.432Z",
|
||||
"updatedAt": "2025-08-26T19:41:03.456Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1021,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "6a0770019fe7c2b4",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "bb189bc3771ff83d",
|
||||
"startTime": "2025-08-26T19:40:59.171Z",
|
||||
"endTime": "2025-08-26T19:41:02.017Z",
|
||||
"name": "node.nodeCallLLM",
|
||||
"metadata": {
|
||||
"langgraph_node": "nodeCallLLM",
|
||||
"langgraph_step": 1,
|
||||
"attributes": {
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"koog.node.name": "nodeCallLLM",
|
||||
"langfuse.observation.metadata.langgraph_node": "nodeCallLLM",
|
||||
"langfuse.observation.metadata.langgraph_step": "1"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:02.422Z",
|
||||
"updatedAt": "2025-08-26T19:41:02.440Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 2846,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "042f9350c3729147",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "6a0770019fe7c2b4",
|
||||
"startTime": "2025-08-26T19:40:59.176Z",
|
||||
"endTime": "2025-08-26T19:41:02.016Z",
|
||||
"name": "llm.chat",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"gen_ai.completion.0.content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "HelloTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_n2HaWg9KFP0BZDqXv6G4b3nJ",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"gen_ai.request.temperature": "1",
|
||||
"gen_ai.prompt.0.role": "system",
|
||||
"gen_ai.completion.0.role": "assistant",
|
||||
"gen_ai.request.model": "gpt-4o",
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"gen_ai.completion.0.finish_reason": "tool_calls",
|
||||
"gen_ai.prompt.0.content": "You are a nice and polite assistant.",
|
||||
"gen_ai.prompt.1.role": "user",
|
||||
"gen_ai.response.finish_reasons": [
|
||||
"tool_calls"
|
||||
],
|
||||
"gen_ai.prompt.1.content": "Greet and say goodbye to the user in the same message. Use provided tools to generate a proper greeting. The user name is Bob.",
|
||||
"gen_ai.completion.1.finish_reason": "tool_calls",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.completion.1.role": "assistant",
|
||||
"gen_ai.completion.1.content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "ByeTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_dOeEHJDI0liHrCdFWTpkTsuO",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"gen_ai.operation.name": "chat"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": {
|
||||
"temperature": 1
|
||||
},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:41:02.328Z",
|
||||
"updatedAt": "2025-08-26T19:41:02.356Z",
|
||||
"usageDetails": {
|
||||
"input": 47,
|
||||
"output": 106,
|
||||
"total": 153
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0.0001175,
|
||||
"output": 0.00106,
|
||||
"total": 0.0011775
|
||||
},
|
||||
"providedCostDetails": {},
|
||||
"model": "gpt-4o",
|
||||
"internalModelId": "b9854a5c92dc496b997d99d20",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 2840,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0.0001175,
|
||||
"outputCost": 0.00106,
|
||||
"totalCost": 0.0011775,
|
||||
"inputUsage": 47,
|
||||
"outputUsage": 106,
|
||||
"totalUsage": 153,
|
||||
"input": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a nice and polite assistant."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Greet and say goodbye to the user in the same message. Use provided tools to generate a proper greeting. The user name is Bob."
|
||||
}
|
||||
],
|
||||
"output": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "HelloTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_n2HaWg9KFP0BZDqXv6G4b3nJ",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"role": "assistant",
|
||||
"finish_reason": "tool_calls"
|
||||
},
|
||||
{
|
||||
"finish_reason": "tool_calls",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"function": {
|
||||
"name": "ByeTool",
|
||||
"arguments": {
|
||||
"name": "Bob"
|
||||
}
|
||||
},
|
||||
"id": "call_dOeEHJDI0liHrCdFWTpkTsuO",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "890e9114a4449257",
|
||||
"traceId": "dff173a675b759ce1b70e522b27d6846",
|
||||
"projectId": "cmcmdwcag00c2ad077xp1qnyc",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "bb189bc3771ff83d",
|
||||
"startTime": "2025-08-26T19:40:59.153Z",
|
||||
"endTime": "2025-08-26T19:40:59.153Z",
|
||||
"name": "node.__start__",
|
||||
"metadata": {
|
||||
"langgraph_node": "__start__",
|
||||
"langgraph_step": 0,
|
||||
"attributes": {
|
||||
"gen_ai.conversation.id": "6f8d2449-33ed-4c7c-82e7-5410e34ee4e6",
|
||||
"koog.node.name": "__start__",
|
||||
"langfuse.observation.metadata.langgraph_node": "__start__",
|
||||
"langfuse.observation.metadata.langgraph_step": "0"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"os.arch": "aarch64",
|
||||
"os.type": "Mac OS X",
|
||||
"os.version": "15.4.1",
|
||||
"service.instance.time": "2025-08-26T19:40:59.130398Z",
|
||||
"service.name": "ai.koog",
|
||||
"service.version": "0.3.0"
|
||||
},
|
||||
"scope": {
|
||||
"name": "ai.koog",
|
||||
"version": "0.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "0.3.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-08-26T19:40:59.897Z",
|
||||
"updatedAt": "2025-08-26T19:40:59.915Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 0,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"trace": {
|
||||
"id": "12ea412956f99347b0503c1144acd0ec",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"name": "llama-index-trace",
|
||||
"timestamp": "2025-06-05T15:45:52.971Z",
|
||||
"environment": "default",
|
||||
"tags": [
|
||||
"llama-index",
|
||||
"rag"
|
||||
],
|
||||
"bookmarked": false,
|
||||
"release": null,
|
||||
"version": "1.0.0",
|
||||
"userId": "user_123",
|
||||
"sessionId": "session_abc",
|
||||
"public": true,
|
||||
"input": "\"What is Langfuse?\"",
|
||||
"output": "\"Langfuse is a tool designed to help developers monitor and debug applications that utilize large language models (LLMs). It provides features for tracking and analyzing the performance of LLMs, enabling developers to gain insights into how these models are functioning within their applications. Langfuse can be particularly useful for identifying issues, optimizing performance, and ensuring that the integration of language models into applications is smooth and effective.\"",
|
||||
"metadata": "{\"email\":\"user@langfuse.com\",\"resourceAttributes\":{\"telemetry.sdk.language\":\"python\",\"telemetry.sdk.name\":\"opentelemetry\",\"telemetry.sdk.version\":\"1.33.1\",\"service.name\":\"unknown_service\"},\"scope\":{\"name\":\"langfuse-sdk\",\"version\":\"3.0.0\",\"attributes\":{\"public_key\":\"pk-lf-5855d85e-3943-497e-bd10-f50ad414bcba\"}}}",
|
||||
"createdAt": "2025-06-05T15:45:56.000Z",
|
||||
"updatedAt": "2025-06-05T15:45:56.029Z",
|
||||
"scores": [],
|
||||
"latency": 2.779
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"id": "3344aabdd4e66110",
|
||||
"traceId": "12ea412956f99347b0503c1144acd0ec",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2025-06-05T15:45:52.971Z",
|
||||
"endTime": "2025-06-05T15:45:55.750Z",
|
||||
"name": "llama-index-trace",
|
||||
"metadata": {
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.33.1",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "langfuse-sdk",
|
||||
"version": "3.0.0",
|
||||
"attributes": {
|
||||
"public_key": "pk-lf-5855d85e-3943-497e-bd10-f50ad414bcba"
|
||||
}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "1.0.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-06-05T15:45:55.996Z",
|
||||
"updatedAt": "2025-06-05T15:45:56.027Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 2779,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "7ca8b22f6628d0c7",
|
||||
"traceId": "12ea412956f99347b0503c1144acd0ec",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "3344aabdd4e66110",
|
||||
"startTime": "2025-06-05T15:45:52.971Z",
|
||||
"endTime": "2025-06-05T15:45:55.747Z",
|
||||
"name": "OpenAI.complete",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"llm.model_name": "gpt-4o",
|
||||
"llm.invocation_parameters": {
|
||||
"context_window": 128000,
|
||||
"num_output": -1,
|
||||
"is_chat_model": true,
|
||||
"is_function_calling_model": true,
|
||||
"model_name": "gpt-4o",
|
||||
"system_role": "system"
|
||||
},
|
||||
"llm.provider": "openai",
|
||||
"llm.system": "openai",
|
||||
"input.value": {
|
||||
"args": [
|
||||
"What is Langfuse?"
|
||||
]
|
||||
},
|
||||
"input.mime_type": "application/json",
|
||||
"llm.prompts": [
|
||||
"What is Langfuse?"
|
||||
],
|
||||
"output.value": "Langfuse is a tool designed to help developers monitor and debug applications that utilize large language models (LLMs). It provides features for tracking and analyzing the performance of LLMs, enabling developers to gain insights into how these models are functioning within their applications. Langfuse can be particularly useful for identifying issues, optimizing performance, and ensuring that the integration of language models into applications is smooth and effective.",
|
||||
"llm.token_count.prompt": "13",
|
||||
"llm.token_count.prompt_details.cache_read": "0",
|
||||
"llm.token_count.prompt_details.audio": "0",
|
||||
"llm.token_count.completion": "81",
|
||||
"llm.token_count.completion_details.reasoning": "0",
|
||||
"llm.token_count.completion_details.audio": "0",
|
||||
"llm.token_count.total": "94",
|
||||
"openinference.span.kind": "LLM"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.33.1",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "openinference.instrumentation.llama_index",
|
||||
"version": "4.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {
|
||||
"context_window": 128000,
|
||||
"num_output": -1,
|
||||
"is_chat_model": true,
|
||||
"is_function_calling_model": true,
|
||||
"model_name": "gpt-4o",
|
||||
"system_role": "system"
|
||||
},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-06-05T15:45:55.866Z",
|
||||
"updatedAt": "2025-06-05T15:45:56.021Z",
|
||||
"usageDetails": {
|
||||
"input": 13,
|
||||
"prompt_details.cache_read": 0,
|
||||
"prompt_details.audio": 0,
|
||||
"output": 81,
|
||||
"completion_details.reasoning": 0,
|
||||
"completion_details.audio": 0,
|
||||
"total": 94
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0.0000325,
|
||||
"output": 0.00081,
|
||||
"total": 0.000842499999
|
||||
},
|
||||
"providedCostDetails": {},
|
||||
"model": "gpt-4o",
|
||||
"internalModelId": "b9854a5c92dc496b997d99d20",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 2776,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0.0000325,
|
||||
"outputCost": 0.00081,
|
||||
"totalCost": 0.000842499999,
|
||||
"inputUsage": 13,
|
||||
"outputUsage": 81,
|
||||
"totalUsage": 94,
|
||||
"input": {
|
||||
"args": [
|
||||
"What is Langfuse?"
|
||||
]
|
||||
},
|
||||
"output": "Langfuse is a tool designed to help developers monitor and debug applications that utilize large language models (LLMs). It provides features for tracking and analyzing the performance of LLMs, enabling developers to gain insights into how these models are functioning within their applications. Langfuse can be particularly useful for identifying issues, optimizing performance, and ensuring that the integration of language models into applications is smooth and effective."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
|
||||
/**
|
||||
* Merges detailed observation data (input/output/metadata) into the base trace file.
|
||||
* how to use? SEE README.md
|
||||
*
|
||||
* Usage: ts-node merge-observations.ts <base-file> <detailed-obs-file> <output-file>
|
||||
*
|
||||
* Example:
|
||||
* ts-node merge-observations.ts pydantic-base.json pydantic-details.json pydantic-ai.json
|
||||
*
|
||||
*/
|
||||
|
||||
const [baseFile, detailedFile, outputFile] = process.argv.slice(2);
|
||||
|
||||
if (!baseFile || !detailedFile || !outputFile) {
|
||||
console.error(
|
||||
"Usage: ts-node merge-observations.ts <base-file> <detailed-obs-file> <output-file>",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseData = JSON.parse(readFileSync(baseFile, "utf-8"));
|
||||
const detailedObs = JSON.parse(readFileSync(detailedFile, "utf-8"));
|
||||
|
||||
// Build ID to detailed observation map
|
||||
const obsMap = new Map<string, any>();
|
||||
for (const obs of Object.values(detailedObs)) {
|
||||
obsMap.set((obs as any).id, obs);
|
||||
}
|
||||
|
||||
// Merge input/output/metadata into observations array
|
||||
const mergedObservations = baseData.observations.map((obs: any) => {
|
||||
const detailed = obsMap.get(obs.id);
|
||||
if (detailed) {
|
||||
return {
|
||||
...obs,
|
||||
input: detailed.input,
|
||||
output: detailed.output,
|
||||
metadata: detailed.metadata,
|
||||
};
|
||||
}
|
||||
return obs;
|
||||
});
|
||||
|
||||
// demo project
|
||||
const PROJECT_ID = "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a";
|
||||
|
||||
// Remove observations from trace, create final structure
|
||||
const output = {
|
||||
trace: {
|
||||
...baseData.trace,
|
||||
projectId: PROJECT_ID,
|
||||
},
|
||||
observations: mergedObservations.map((obs: any) => ({
|
||||
...obs,
|
||||
projectId: PROJECT_ID,
|
||||
})),
|
||||
};
|
||||
|
||||
// Remove observations array from trace if it exists
|
||||
delete (output.trace as any).observations;
|
||||
|
||||
writeFileSync(outputFile, JSON.stringify(output, null, 2));
|
||||
|
||||
console.log(`Merged ${mergedObservations.length} observations`);
|
||||
console.log(`Wrote to ${outputFile}`);
|
||||
@@ -0,0 +1,931 @@
|
||||
{
|
||||
"trace": {
|
||||
"id": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"name": "Agent workflow",
|
||||
"timestamp": "2025-09-30T09:03:59.962Z",
|
||||
"environment": "default",
|
||||
"tags": [],
|
||||
"bookmarked": false,
|
||||
"release": null,
|
||||
"version": null,
|
||||
"userId": null,
|
||||
"sessionId": null,
|
||||
"public": true,
|
||||
"input": null,
|
||||
"output": null,
|
||||
"metadata": "{\"attributes\":{\"openinference.span.kind\":\"AGENT\"},\"resourceAttributes\":{\"telemetry.sdk.language\":\"python\",\"telemetry.sdk.name\":\"opentelemetry\",\"telemetry.sdk.version\":\"1.37.0\",\"service.name\":\"unknown_service\"},\"scope\":{\"name\":\"openinference.instrumentation.openai_agents\",\"version\":\"1.3.0\",\"attributes\":{}}}",
|
||||
"createdAt": "2025-09-30T09:04:03.000Z",
|
||||
"updatedAt": "2025-09-30T09:04:07.465Z",
|
||||
"scores": [
|
||||
{
|
||||
"id": "0721bd4f-a35c-4d29-a2c6-38879e1d758e",
|
||||
"timestamp": "2025-09-30T09:04:30.125Z",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"environment": "default",
|
||||
"name": "toxicity",
|
||||
"source": "EVAL",
|
||||
"authorUserId": null,
|
||||
"comment": "The generation contains aggressive and dismissive language that undermines trust in professionals and promotes a negative view of the public's intelligence, which is harmful and disrespectful.",
|
||||
"metadata": "{}",
|
||||
"configId": null,
|
||||
"createdAt": "2025-09-30T09:04:31.000Z",
|
||||
"updatedAt": "2025-09-30T09:04:30.197Z",
|
||||
"queueId": null,
|
||||
"traceId": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"observationId": null,
|
||||
"sessionId": null,
|
||||
"datasetRunId": null,
|
||||
"value": 0.8,
|
||||
"stringValue": null,
|
||||
"dataType": "NUMERIC"
|
||||
},
|
||||
{
|
||||
"id": "f60dc1c4-17af-4512-ada9-f2440f437ce1",
|
||||
"timestamp": "2025-09-30T09:04:28.687Z",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"environment": "default",
|
||||
"name": "hallucination",
|
||||
"source": "EVAL",
|
||||
"authorUserId": null,
|
||||
"comment": "There is no specific generation provided to evaluate for hallucination. Therefore, it is impossible to assess or score the degree of hallucination in this case.",
|
||||
"metadata": "{}",
|
||||
"configId": null,
|
||||
"createdAt": "2025-09-30T09:04:29.000Z",
|
||||
"updatedAt": "2025-09-30T09:04:28.773Z",
|
||||
"queueId": null,
|
||||
"traceId": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"observationId": null,
|
||||
"sessionId": null,
|
||||
"datasetRunId": null,
|
||||
"value": 0,
|
||||
"stringValue": null,
|
||||
"dataType": "NUMERIC"
|
||||
}
|
||||
],
|
||||
"latency": 2.19
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"id": "e5a76f27f51ad40e",
|
||||
"traceId": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "AGENT",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2025-09-30T09:03:59.962Z",
|
||||
"endTime": "2025-09-30T09:04:02.152Z",
|
||||
"name": "Agent workflow",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"openinference.span.kind": "AGENT"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.37.0",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "openinference.instrumentation.openai_agents",
|
||||
"version": "1.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-09-30T09:04:07.382Z",
|
||||
"updatedAt": "2025-09-30T09:04:07.382Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 2190,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "d950b668796240b5",
|
||||
"traceId": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "AGENT",
|
||||
"environment": "default",
|
||||
"parentObservationId": "e5a76f27f51ad40e",
|
||||
"startTime": "2025-09-30T09:03:59.962Z",
|
||||
"endTime": "2025-09-30T09:04:02.152Z",
|
||||
"name": "Hello world",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"llm.system": "openai",
|
||||
"graph.node.id": "Hello world",
|
||||
"openinference.span.kind": "AGENT"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.37.0",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "openinference.instrumentation.openai_agents",
|
||||
"version": "1.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-09-30T09:04:07.382Z",
|
||||
"updatedAt": "2025-09-30T09:04:07.382Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 2190,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"id": "90d94774e8e3724d",
|
||||
"traceId": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "d950b668796240b5",
|
||||
"startTime": "2025-09-30T09:04:01.001Z",
|
||||
"endTime": "2025-09-30T09:04:02.151Z",
|
||||
"name": "response",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"llm.system": "openai",
|
||||
"output.mime_type": "application/json",
|
||||
"output.value": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d0110d8819da676bd1f2dd03a40",
|
||||
"created_at": 1759223041,
|
||||
"error": null,
|
||||
"incomplete_details": null,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"object": "response",
|
||||
"output": [
|
||||
{
|
||||
"id": "msg_0f00ca5b7e22bb4c0068db9d019a78819d9fe1e4d3b6c96b68",
|
||||
"content": [
|
||||
{
|
||||
"annotations": [],
|
||||
"text": "The weather in Tokyo is currently sunny. If you need more details like temperature or forecast for the upcoming days, just let me know!",
|
||||
"type": "output_text",
|
||||
"logprobs": []
|
||||
}
|
||||
],
|
||||
"role": "assistant",
|
||||
"status": "completed",
|
||||
"type": "message"
|
||||
}
|
||||
],
|
||||
"parallel_tool_calls": true,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"title": "City",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
"title": "get_weather_args",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"strict": true,
|
||||
"type": "function",
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"top_p": 1,
|
||||
"background": false,
|
||||
"conversation": null,
|
||||
"max_output_tokens": null,
|
||||
"max_tool_calls": null,
|
||||
"previous_response_id": null,
|
||||
"prompt": null,
|
||||
"prompt_cache_key": null,
|
||||
"reasoning": {
|
||||
"effort": null,
|
||||
"generate_summary": null,
|
||||
"summary": null
|
||||
},
|
||||
"safety_identifier": null,
|
||||
"service_tier": "default",
|
||||
"status": "completed",
|
||||
"text": {
|
||||
"format": {
|
||||
"type": "text"
|
||||
},
|
||||
"verbosity": "medium"
|
||||
},
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"usage": {
|
||||
"input_tokens": 85,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 0
|
||||
},
|
||||
"output_tokens": 29,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 0
|
||||
},
|
||||
"total_tokens": 114
|
||||
},
|
||||
"user": null,
|
||||
"billing": {
|
||||
"payer": "developer"
|
||||
},
|
||||
"store": true
|
||||
},
|
||||
"llm.tools.0.tool.json_schema": {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": null,
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"title": "City",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
"title": "get_weather_args",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"llm.token_count.completion": "29",
|
||||
"llm.token_count.prompt": "85",
|
||||
"llm.token_count.total": "114",
|
||||
"llm.token_count.prompt_details.cache_read": "0",
|
||||
"llm.token_count.completion_details.reasoning": "0",
|
||||
"llm.output_messages.0.message.role": "assistant",
|
||||
"llm.output_messages.0.message.contents.0.message_content.type": "text",
|
||||
"llm.output_messages.0.message.contents.0.message_content.text": "The weather in Tokyo is currently sunny. If you need more details like temperature or forecast for the upcoming days, just let me know!",
|
||||
"llm.input_messages.0.message.role": "system",
|
||||
"llm.input_messages.0.message.content": "You are a helpful agent.",
|
||||
"llm.model_name": "gpt-4.1-2025-04-14",
|
||||
"llm.invocation_parameters": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d0110d8819da676bd1f2dd03a40",
|
||||
"created_at": 1759223041,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"parallel_tool_calls": true,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"top_p": 1,
|
||||
"background": false,
|
||||
"reasoning": {},
|
||||
"service_tier": "default",
|
||||
"text": {
|
||||
"format": {
|
||||
"type": "text"
|
||||
},
|
||||
"verbosity": "medium"
|
||||
},
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"billing": {
|
||||
"payer": "developer"
|
||||
},
|
||||
"store": true
|
||||
},
|
||||
"input.mime_type": "application/json",
|
||||
"input.value": [
|
||||
{
|
||||
"content": "What's the weather in Tokyo?",
|
||||
"role": "user"
|
||||
},
|
||||
{
|
||||
"arguments": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"call_id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"name": "get_weather",
|
||||
"type": "function_call",
|
||||
"id": "fc_0f00ca5b7e22bb4c0068db9d00d258819d987f4b9b63189e76",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"call_id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"output": "The weather in Tokyo is sunny.",
|
||||
"type": "function_call_output"
|
||||
}
|
||||
],
|
||||
"llm.input_messages.1.message.role": "user",
|
||||
"llm.input_messages.1.message.content": "What's the weather in Tokyo?",
|
||||
"llm.input_messages.2.message.role": "assistant",
|
||||
"llm.input_messages.2.message.tool_calls.0.tool_call.id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"llm.input_messages.2.message.tool_calls.0.tool_call.function.name": "get_weather",
|
||||
"llm.input_messages.2.message.tool_calls.0.tool_call.function.arguments": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"llm.input_messages.3.message.role": "tool",
|
||||
"llm.input_messages.3.message.tool_call_id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"llm.input_messages.3.message.content": "The weather in Tokyo is sunny.",
|
||||
"openinference.span.kind": "LLM"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.37.0",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "openinference.instrumentation.openai_agents",
|
||||
"version": "1.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d0110d8819da676bd1f2dd03a40",
|
||||
"created_at": 1759223041,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": "{}",
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"parallel_tool_calls": "true",
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"top_p": 1,
|
||||
"background": "false",
|
||||
"reasoning": "{}",
|
||||
"service_tier": "default",
|
||||
"text": "{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"}",
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"billing": "{\"payer\":\"developer\"}",
|
||||
"store": "true"
|
||||
},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-09-30T09:04:07.382Z",
|
||||
"updatedAt": "2025-09-30T09:04:07.382Z",
|
||||
"usageDetails": {
|
||||
"output": 29,
|
||||
"input": 85,
|
||||
"total": 114,
|
||||
"prompt_details.cache_read": 0,
|
||||
"completion_details.reasoning": 0
|
||||
},
|
||||
"costDetails": {
|
||||
"output": 0.000232,
|
||||
"input": 0.00017,
|
||||
"total": 0.000402
|
||||
},
|
||||
"providedCostDetails": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"internalModelId": "cm7qahw732891bpmzy45r3x70",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1150,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0.00017,
|
||||
"outputCost": 0.000232,
|
||||
"totalCost": 0.000402,
|
||||
"inputUsage": 85,
|
||||
"outputUsage": 29,
|
||||
"totalUsage": 114,
|
||||
"input": [
|
||||
{
|
||||
"content": "What's the weather in Tokyo?",
|
||||
"role": "user"
|
||||
},
|
||||
{
|
||||
"arguments": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"call_id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"name": "get_weather",
|
||||
"type": "function_call",
|
||||
"id": "fc_0f00ca5b7e22bb4c0068db9d00d258819d987f4b9b63189e76",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"call_id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"output": "The weather in Tokyo is sunny.",
|
||||
"type": "function_call_output"
|
||||
}
|
||||
],
|
||||
"output": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d0110d8819da676bd1f2dd03a40",
|
||||
"created_at": 1759223041,
|
||||
"error": null,
|
||||
"incomplete_details": null,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"object": "response",
|
||||
"output": [
|
||||
{
|
||||
"id": "msg_0f00ca5b7e22bb4c0068db9d019a78819d9fe1e4d3b6c96b68",
|
||||
"content": [
|
||||
{
|
||||
"annotations": [],
|
||||
"text": "The weather in Tokyo is currently sunny. If you need more details like temperature or forecast for the upcoming days, just let me know!",
|
||||
"type": "output_text",
|
||||
"logprobs": []
|
||||
}
|
||||
],
|
||||
"role": "assistant",
|
||||
"status": "completed",
|
||||
"type": "message"
|
||||
}
|
||||
],
|
||||
"parallel_tool_calls": true,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"title": "City",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
"title": "get_weather_args",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"strict": true,
|
||||
"type": "function",
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"top_p": 1,
|
||||
"background": false,
|
||||
"conversation": null,
|
||||
"max_output_tokens": null,
|
||||
"max_tool_calls": null,
|
||||
"previous_response_id": null,
|
||||
"prompt": null,
|
||||
"prompt_cache_key": null,
|
||||
"reasoning": {
|
||||
"effort": null,
|
||||
"generate_summary": null,
|
||||
"summary": null
|
||||
},
|
||||
"safety_identifier": null,
|
||||
"service_tier": "default",
|
||||
"status": "completed",
|
||||
"text": {
|
||||
"format": {
|
||||
"type": "text"
|
||||
},
|
||||
"verbosity": "medium"
|
||||
},
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"usage": {
|
||||
"input_tokens": 85,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 0
|
||||
},
|
||||
"output_tokens": 29,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 0
|
||||
},
|
||||
"total_tokens": 114
|
||||
},
|
||||
"user": null,
|
||||
"billing": {
|
||||
"payer": "developer"
|
||||
},
|
||||
"store": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "98870087af69bf06",
|
||||
"traceId": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "d950b668796240b5",
|
||||
"startTime": "2025-09-30T09:03:59.963Z",
|
||||
"endTime": "2025-09-30T09:04:00.999Z",
|
||||
"name": "response",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"llm.system": "openai",
|
||||
"output.mime_type": "application/json",
|
||||
"output.value": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d000be4819d824c447c54b65bc1",
|
||||
"created_at": 1759223040,
|
||||
"error": null,
|
||||
"incomplete_details": null,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"object": "response",
|
||||
"output": [
|
||||
{
|
||||
"arguments": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"call_id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"name": "get_weather",
|
||||
"type": "function_call",
|
||||
"id": "fc_0f00ca5b7e22bb4c0068db9d00d258819d987f4b9b63189e76",
|
||||
"status": "completed"
|
||||
}
|
||||
],
|
||||
"parallel_tool_calls": true,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"title": "City",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
"title": "get_weather_args",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"strict": true,
|
||||
"type": "function",
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"top_p": 1,
|
||||
"background": false,
|
||||
"conversation": null,
|
||||
"max_output_tokens": null,
|
||||
"max_tool_calls": null,
|
||||
"previous_response_id": null,
|
||||
"prompt": null,
|
||||
"prompt_cache_key": null,
|
||||
"reasoning": {
|
||||
"effort": null,
|
||||
"generate_summary": null,
|
||||
"summary": null
|
||||
},
|
||||
"safety_identifier": null,
|
||||
"service_tier": "default",
|
||||
"status": "completed",
|
||||
"text": {
|
||||
"format": {
|
||||
"type": "text"
|
||||
},
|
||||
"verbosity": "medium"
|
||||
},
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"usage": {
|
||||
"input_tokens": 55,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 0
|
||||
},
|
||||
"output_tokens": 15,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 0
|
||||
},
|
||||
"total_tokens": 70
|
||||
},
|
||||
"user": null,
|
||||
"billing": {
|
||||
"payer": "developer"
|
||||
},
|
||||
"store": true
|
||||
},
|
||||
"llm.tools.0.tool.json_schema": {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": null,
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"title": "City",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
"title": "get_weather_args",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"llm.token_count.completion": "15",
|
||||
"llm.token_count.prompt": "55",
|
||||
"llm.token_count.total": "70",
|
||||
"llm.token_count.prompt_details.cache_read": "0",
|
||||
"llm.token_count.completion_details.reasoning": "0",
|
||||
"llm.output_messages.0.message.role": "assistant",
|
||||
"llm.output_messages.0.message.tool_calls.0.tool_call.id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"llm.output_messages.0.message.tool_calls.0.tool_call.function.name": "get_weather",
|
||||
"llm.output_messages.0.message.tool_calls.0.tool_call.function.arguments": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"llm.input_messages.0.message.role": "system",
|
||||
"llm.input_messages.0.message.content": "You are a helpful agent.",
|
||||
"llm.model_name": "gpt-4.1-2025-04-14",
|
||||
"llm.invocation_parameters": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d000be4819d824c447c54b65bc1",
|
||||
"created_at": 1759223040,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"parallel_tool_calls": true,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"top_p": 1,
|
||||
"background": false,
|
||||
"reasoning": {},
|
||||
"service_tier": "default",
|
||||
"text": {
|
||||
"format": {
|
||||
"type": "text"
|
||||
},
|
||||
"verbosity": "medium"
|
||||
},
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"billing": {
|
||||
"payer": "developer"
|
||||
},
|
||||
"store": true
|
||||
},
|
||||
"input.mime_type": "application/json",
|
||||
"input.value": [
|
||||
{
|
||||
"content": "What's the weather in Tokyo?",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"llm.input_messages.1.message.role": "user",
|
||||
"llm.input_messages.1.message.content": "What's the weather in Tokyo?",
|
||||
"openinference.span.kind": "LLM"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.37.0",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "openinference.instrumentation.openai_agents",
|
||||
"version": "1.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d000be4819d824c447c54b65bc1",
|
||||
"created_at": 1759223040,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": "{}",
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"parallel_tool_calls": "true",
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"top_p": 1,
|
||||
"background": "false",
|
||||
"reasoning": "{}",
|
||||
"service_tier": "default",
|
||||
"text": "{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"}",
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"billing": "{\"payer\":\"developer\"}",
|
||||
"store": "true"
|
||||
},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-09-30T09:04:02.217Z",
|
||||
"updatedAt": "2025-09-30T09:04:02.217Z",
|
||||
"usageDetails": {
|
||||
"output": 15,
|
||||
"input": 55,
|
||||
"total": 70,
|
||||
"prompt_details.cache_read": 0,
|
||||
"completion_details.reasoning": 0
|
||||
},
|
||||
"costDetails": {
|
||||
"output": 0.00012,
|
||||
"input": 0.00011,
|
||||
"total": 0.00023
|
||||
},
|
||||
"providedCostDetails": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"internalModelId": "cm7qahw732891bpmzy45r3x70",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1036,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0.00011,
|
||||
"outputCost": 0.00012,
|
||||
"totalCost": 0.00023,
|
||||
"inputUsage": 55,
|
||||
"outputUsage": 15,
|
||||
"totalUsage": 70,
|
||||
"input": [
|
||||
{
|
||||
"content": "What's the weather in Tokyo?",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"output": {
|
||||
"id": "resp_0f00ca5b7e22bb4c0068db9d000be4819d824c447c54b65bc1",
|
||||
"created_at": 1759223040,
|
||||
"error": null,
|
||||
"incomplete_details": null,
|
||||
"instructions": "You are a helpful agent.",
|
||||
"metadata": {},
|
||||
"model": "gpt-4.1-2025-04-14",
|
||||
"object": "response",
|
||||
"output": [
|
||||
{
|
||||
"arguments": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"call_id": "call_Kud0j0DxWSmLzv9W5m6qVqXn",
|
||||
"name": "get_weather",
|
||||
"type": "function_call",
|
||||
"id": "fc_0f00ca5b7e22bb4c0068db9d00d258819d987f4b9b63189e76",
|
||||
"status": "completed"
|
||||
}
|
||||
],
|
||||
"parallel_tool_calls": true,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"title": "City",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["city"],
|
||||
"title": "get_weather_args",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"strict": true,
|
||||
"type": "function",
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"top_p": 1,
|
||||
"background": false,
|
||||
"conversation": null,
|
||||
"max_output_tokens": null,
|
||||
"max_tool_calls": null,
|
||||
"previous_response_id": null,
|
||||
"prompt": null,
|
||||
"prompt_cache_key": null,
|
||||
"reasoning": {
|
||||
"effort": null,
|
||||
"generate_summary": null,
|
||||
"summary": null
|
||||
},
|
||||
"safety_identifier": null,
|
||||
"service_tier": "default",
|
||||
"status": "completed",
|
||||
"text": {
|
||||
"format": {
|
||||
"type": "text"
|
||||
},
|
||||
"verbosity": "medium"
|
||||
},
|
||||
"top_logprobs": 0,
|
||||
"truncation": "disabled",
|
||||
"usage": {
|
||||
"input_tokens": 55,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 0
|
||||
},
|
||||
"output_tokens": 15,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 0
|
||||
},
|
||||
"total_tokens": 70
|
||||
},
|
||||
"user": null,
|
||||
"billing": {
|
||||
"payer": "developer"
|
||||
},
|
||||
"store": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5b99c3e3411ed17b",
|
||||
"traceId": "fee618f96dc31e0ca38b2f7b26eb8b29",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "TOOL",
|
||||
"environment": "default",
|
||||
"parentObservationId": "d950b668796240b5",
|
||||
"startTime": "2025-09-30T09:04:01.000Z",
|
||||
"endTime": "2025-09-30T09:04:01.000Z",
|
||||
"name": "get_weather",
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"llm.system": "openai",
|
||||
"tool.name": "get_weather",
|
||||
"input.value": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"input.mime_type": "application/json",
|
||||
"output.value": "The weather in Tokyo is sunny.",
|
||||
"openinference.span.kind": "TOOL"
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.37.0",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "openinference.instrumentation.openai_agents",
|
||||
"version": "1.3.0",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-09-30T09:04:02.217Z",
|
||||
"updatedAt": "2025-09-30T09:04:02.217Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 0,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": {
|
||||
"city": "Tokyo"
|
||||
},
|
||||
"output": "The weather in Tokyo is sunny."
|
||||
}
|
||||
]
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
{
|
||||
"trace": {
|
||||
"id": "b3b7b128-5664-4f42-9fab-31999da9e2f1",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"name": "run_math_tutor",
|
||||
"timestamp": "2024-05-29T13:46:10.728Z",
|
||||
"environment": "default",
|
||||
"tags": [],
|
||||
"bookmarked": false,
|
||||
"release": null,
|
||||
"version": null,
|
||||
"userId": null,
|
||||
"sessionId": null,
|
||||
"public": true,
|
||||
"input": "\"{\\\"args\\\":[\\\"I need to solve the equation `3x + 11 = 14`. Can you help me?\\\"],\\\"kwargs\\\":{}}\"",
|
||||
"output": "\"\\\"Sure, first subtract 11 from both sides to get `3x = 3`, then divide both sides by 3 to solve for `x`. The solution is `x = 1`.\\\"\"",
|
||||
"metadata": "{}",
|
||||
"createdAt": "2024-05-29T13:46:11.304Z",
|
||||
"updatedAt": "2024-05-29T13:46:34.171Z",
|
||||
"latency": 9.236
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"id": "0d8f5427-c91a-44c8-86c5-e12d806edd57",
|
||||
"traceId": "b3b7b128-5664-4f42-9fab-31999da9e2f1",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "0d6c73e1-be16-436d-a905-b4108912fa93",
|
||||
"startTime": "2024-05-29T13:46:19.963Z",
|
||||
"endTime": null,
|
||||
"name": "",
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2024-05-29T13:46:20.714Z",
|
||||
"updatedAt": "2024-05-29T13:46:20.714Z",
|
||||
"usageDetails": {
|
||||
"input": 49,
|
||||
"output": 40,
|
||||
"total": 89
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0.00147,
|
||||
"output": 0.0024,
|
||||
"total": 0.00387
|
||||
},
|
||||
"providedCostDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"model": "gpt-4",
|
||||
"internalModelId": "clrntkjgy000f08jx79v9g1xj",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": null,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0.00147,
|
||||
"outputCost": 0.0024,
|
||||
"totalCost": 0.00387,
|
||||
"inputUsage": 49,
|
||||
"outputUsage": 40,
|
||||
"totalUsage": 89,
|
||||
"input": [
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "I am a math tutor that likes to help math students, how can I help?"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I need to solve the equation `3x + 11 = 14`. Can you help me?"
|
||||
}
|
||||
],
|
||||
"output": "Sure, first subtract 11 from both sides to get `3x = 3`, then divide both sides by 3 to solve for `x`. The solution is `x = 1`.",
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"id": "0d6c73e1-be16-436d-a905-b4108912fa93",
|
||||
"traceId": "b3b7b128-5664-4f42-9fab-31999da9e2f1",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2024-05-29T13:46:17.971Z",
|
||||
"endTime": "2024-05-29T13:46:19.964Z",
|
||||
"name": "get_response",
|
||||
"metadata": "{}",
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2024-05-29T13:46:18.345Z",
|
||||
"updatedAt": "2024-05-29T13:46:20.796Z",
|
||||
"usageDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"providedCostDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1993,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0,
|
||||
"outputCost": 0,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": {
|
||||
"args": [
|
||||
"thread_fbFB5caAj0sEpE1c5ICMDs7I",
|
||||
"run_RsJmiMTrgkBzb3FvyCvRoqpd"
|
||||
],
|
||||
"kwargs": {}
|
||||
},
|
||||
"output": [
|
||||
"Sure, first subtract 11 from both sides to get `3x = 3`, then divide both sides by 3 to solve for `x`. The solution is `x = 1`.",
|
||||
{
|
||||
"id": "run_AherTwpS29w0GC8zlKDBhhlZ",
|
||||
"model": "gpt-4",
|
||||
"tools": [],
|
||||
"top_p": 1,
|
||||
"usage": null,
|
||||
"object": "thread.run",
|
||||
"status": "queued",
|
||||
"metadata": {},
|
||||
"failed_at": null,
|
||||
"thread_id": "thread_DhsY59BBhtKWN8YYq44cULrH",
|
||||
"created_at": 1716990330,
|
||||
"expires_at": 1716990930,
|
||||
"last_error": null,
|
||||
"started_at": null,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"assistant_id": "asst_tjgFtRASnPoQaWpks1ezPfSp",
|
||||
"cancelled_at": null,
|
||||
"completed_at": null,
|
||||
"instructions": "You are a personal math tutor. Answer questions briefly, in a sentence or less.",
|
||||
"tool_resources": {},
|
||||
"required_action": null,
|
||||
"response_format": "auto",
|
||||
"max_prompt_tokens": null,
|
||||
"incomplete_details": null,
|
||||
"truncation_strategy": {
|
||||
"type": "auto",
|
||||
"last_messages": null
|
||||
},
|
||||
"max_completion_tokens": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e584f95b-e8ef-4d43-bcb1-fd296f57f94b",
|
||||
"traceId": "b3b7b128-5664-4f42-9fab-31999da9e2f1",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2024-05-29T13:46:11.057Z",
|
||||
"endTime": "2024-05-29T13:46:12.959Z",
|
||||
"name": "run_assistant",
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2024-05-29T13:46:11.480Z",
|
||||
"updatedAt": "2024-05-29T13:46:13.161Z",
|
||||
"usageDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"providedCostDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 1902,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0,
|
||||
"outputCost": 0,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": {
|
||||
"args": [
|
||||
"asst_01wKwjx3rT7CNprPxzE8azMl",
|
||||
"I need to solve the equation `3x + 11 = 14`. Can you help me?"
|
||||
],
|
||||
"kwargs": {}
|
||||
},
|
||||
"output": [
|
||||
{
|
||||
"id": "run_RsJmiMTrgkBzb3FvyCvRoqpd",
|
||||
"model": "gpt-4",
|
||||
"tools": [],
|
||||
"top_p": 1,
|
||||
"usage": null,
|
||||
"object": "thread.run",
|
||||
"status": "queued",
|
||||
"metadata": {},
|
||||
"failed_at": null,
|
||||
"thread_id": "thread_fbFB5caAj0sEpE1c5ICMDs7I",
|
||||
"created_at": 1716990372,
|
||||
"expires_at": 1716990972,
|
||||
"last_error": null,
|
||||
"started_at": null,
|
||||
"temperature": 1,
|
||||
"tool_choice": "auto",
|
||||
"assistant_id": "asst_01wKwjx3rT7CNprPxzE8azMl",
|
||||
"cancelled_at": null,
|
||||
"completed_at": null,
|
||||
"instructions": "You are a personal math tutor. Answer questions briefly, in a sentence or less.",
|
||||
"tool_resources": {},
|
||||
"required_action": null,
|
||||
"response_format": "auto",
|
||||
"max_prompt_tokens": null,
|
||||
"incomplete_details": null,
|
||||
"truncation_strategy": {
|
||||
"type": "auto",
|
||||
"last_messages": null
|
||||
},
|
||||
"max_completion_tokens": null
|
||||
},
|
||||
{
|
||||
"id": "thread_fbFB5caAj0sEpE1c5ICMDs7I",
|
||||
"object": "thread",
|
||||
"metadata": {},
|
||||
"created_at": 1716990371,
|
||||
"tool_resources": {
|
||||
"file_search": null,
|
||||
"code_interpreter": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"id": "be59b862-98dd-4b0e-b92e-c150b9777c2f",
|
||||
"traceId": "b3b7b128-5664-4f42-9fab-31999da9e2f1",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2024-05-29T13:46:10.728Z",
|
||||
"endTime": "2024-05-29T13:46:11.056Z",
|
||||
"name": "create_assistant",
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2024-05-29T13:46:11.350Z",
|
||||
"updatedAt": "2024-05-29T13:46:11.405Z",
|
||||
"usageDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"providedCostDetails": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"total": 0
|
||||
},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 328,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0,
|
||||
"outputCost": 0,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": {
|
||||
"args": [],
|
||||
"kwargs": {}
|
||||
},
|
||||
"output": {
|
||||
"id": "asst_01wKwjx3rT7CNprPxzE8azMl",
|
||||
"name": "Math Tutor",
|
||||
"model": "gpt-4",
|
||||
"tools": [],
|
||||
"top_p": 1,
|
||||
"object": "assistant",
|
||||
"metadata": {},
|
||||
"created_at": 1716990370,
|
||||
"description": null,
|
||||
"temperature": 1,
|
||||
"instructions": "You are a personal math tutor. Answer questions briefly, in a sentence or less.",
|
||||
"tool_resources": {
|
||||
"file_search": null,
|
||||
"code_interpreter": null
|
||||
},
|
||||
"response_format": "auto"
|
||||
},
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
{
|
||||
"trace": {
|
||||
"id": "25f4bdeebaab60e6e1bee7e8469554bc",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"name": "pydantic-ai-qa-trace",
|
||||
"timestamp": "2025-06-06T14:40:28.562Z",
|
||||
"environment": "default",
|
||||
"tags": ["dev", "pydantic-ai"],
|
||||
"bookmarked": false,
|
||||
"release": null,
|
||||
"version": "1.0.0",
|
||||
"userId": "user_123",
|
||||
"sessionId": "session_abc",
|
||||
"public": true,
|
||||
"input": "\"What is Langfuse?\"",
|
||||
"output": "\"Langfuse is a tool designed for observability and monitoring of applications that utilize large language models (LLMs). It provides features to trace, log, and visualize requests, helping developers understand the behavior of their LLM-powered applications more effectively. This can be particularly useful for debugging, performance optimization, and ensuring reliable operation of systems that depend on LLMs. Langfuse is designed to integrate easily with existing infrastructure, supporting both self-hosted implementations and cloud-based solutions.\"",
|
||||
"metadata": "{\"email\":\"user@langfuse.com\",\"resourceAttributes\":{\"telemetry.sdk.language\":\"python\",\"telemetry.sdk.name\":\"opentelemetry\",\"telemetry.sdk.version\":\"1.33.1\",\"service.name\":\"unknown_service\"},\"scope\":{\"name\":\"langfuse-sdk\",\"version\":\"3.0.0\",\"attributes\":{\"public_key\":\"pk-lf-5855d85e-3943-497e-bd10-f50ad414bcba\"}}}",
|
||||
"createdAt": "2025-06-06T14:40:34.000Z",
|
||||
"updatedAt": "2025-06-06T14:40:33.635Z",
|
||||
"latency": 4.795
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"id": "538fed87d11686d3",
|
||||
"traceId": "25f4bdeebaab60e6e1bee7e8469554bc",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": null,
|
||||
"startTime": "2025-06-06T14:40:28.562Z",
|
||||
"endTime": "2025-06-06T14:40:33.357Z",
|
||||
"name": "pydantic-ai-qa-trace",
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": "1.0.0",
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-06-06T14:40:33.595Z",
|
||||
"updatedAt": "2025-06-06T14:40:33.630Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 4795,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": null,
|
||||
"metadata": {
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.33.1",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "langfuse-sdk",
|
||||
"version": "3.0.0",
|
||||
"attributes": {
|
||||
"public_key": "pk-lf-5855d85e-3943-497e-bd10-f50ad414bcba"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "0a4fac2553f22f2e",
|
||||
"traceId": "25f4bdeebaab60e6e1bee7e8469554bc",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "SPAN",
|
||||
"environment": "default",
|
||||
"parentObservationId": "538fed87d11686d3",
|
||||
"startTime": "2025-06-06T14:40:28.562Z",
|
||||
"endTime": "2025-06-06T14:40:33.357Z",
|
||||
"name": "qa_agent run",
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": null,
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-06-06T14:40:33.596Z",
|
||||
"updatedAt": "2025-06-06T14:40:33.641Z",
|
||||
"usageDetails": {},
|
||||
"costDetails": {},
|
||||
"providedCostDetails": {},
|
||||
"model": null,
|
||||
"internalModelId": null,
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 4795,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": null,
|
||||
"outputCost": null,
|
||||
"totalCost": 0,
|
||||
"inputUsage": 0,
|
||||
"outputUsage": 0,
|
||||
"totalUsage": 0,
|
||||
"input": null,
|
||||
"output": [
|
||||
{
|
||||
"content": "You are a helpful assistant that answers questions clearly and concisely.",
|
||||
"role": "system",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.system.message"
|
||||
},
|
||||
{
|
||||
"content": "What is Langfuse?",
|
||||
"role": "user",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.user.message"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Langfuse is a tool designed for observability and monitoring of applications that utilize large language models (LLMs). It provides features to trace, log, and visualize requests, helping developers understand the behavior of their LLM-powered applications more effectively. This can be particularly useful for debugging, performance optimization, and ensuring reliable operation of systems that depend on LLMs. Langfuse is designed to integrate easily with existing infrastructure, supporting both self-hosted implementations and cloud-based solutions.",
|
||||
"gen_ai.message.index": 1,
|
||||
"event.name": "gen_ai.assistant.message"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"model_name": "gpt-4o",
|
||||
"agent_name": "qa_agent",
|
||||
"logfire.msg": "qa_agent run",
|
||||
"final_result": "Langfuse is a tool designed for observability and monitoring of applications that utilize large language models (LLMs). It provides features to trace, log, and visualize requests, helping developers understand the behavior of their LLM-powered applications more effectively. This can be particularly useful for debugging, performance optimization, and ensuring reliable operation of systems that depend on LLMs. Langfuse is designed to integrate easily with existing infrastructure, supporting both self-hosted implementations and cloud-based solutions.",
|
||||
"gen_ai.usage.input_tokens": "31",
|
||||
"gen_ai.usage.output_tokens": "96",
|
||||
"all_messages_events": [
|
||||
{
|
||||
"content": "You are a helpful assistant that answers questions clearly and concisely.",
|
||||
"role": "system",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.system.message"
|
||||
},
|
||||
{
|
||||
"content": "What is Langfuse?",
|
||||
"role": "user",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.user.message"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Langfuse is a tool designed for observability and monitoring of applications that utilize large language models (LLMs). It provides features to trace, log, and visualize requests, helping developers understand the behavior of their LLM-powered applications more effectively. This can be particularly useful for debugging, performance optimization, and ensuring reliable operation of systems that depend on LLMs. Langfuse is designed to integrate easily with existing infrastructure, supporting both self-hosted implementations and cloud-based solutions.",
|
||||
"gen_ai.message.index": 1,
|
||||
"event.name": "gen_ai.assistant.message"
|
||||
}
|
||||
],
|
||||
"logfire.json_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all_messages_events": {
|
||||
"type": "array"
|
||||
},
|
||||
"final_result": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.33.1",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "pydantic-ai",
|
||||
"version": "0.2.15",
|
||||
"attributes": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5cb389a3bdd21a6e",
|
||||
"traceId": "25f4bdeebaab60e6e1bee7e8469554bc",
|
||||
"projectId": "cloramnkj0002jz088vzn1ja4",
|
||||
"type": "GENERATION",
|
||||
"environment": "default",
|
||||
"parentObservationId": "0a4fac2553f22f2e",
|
||||
"startTime": "2025-06-06T14:40:28.562Z",
|
||||
"endTime": "2025-06-06T14:40:33.356Z",
|
||||
"name": "chat gpt-4o",
|
||||
"level": "DEFAULT",
|
||||
"statusMessage": null,
|
||||
"version": null,
|
||||
"modelParameters": {},
|
||||
"completionStartTime": null,
|
||||
"promptId": null,
|
||||
"createdAt": "2025-06-06T14:40:33.440Z",
|
||||
"updatedAt": "2025-06-06T14:40:33.478Z",
|
||||
"usageDetails": {
|
||||
"input": 31,
|
||||
"output": 96,
|
||||
"total": 127
|
||||
},
|
||||
"costDetails": {
|
||||
"input": 0.0000775,
|
||||
"output": 0.00096,
|
||||
"total": 0.0010375
|
||||
},
|
||||
"providedCostDetails": {},
|
||||
"model": "gpt-4o",
|
||||
"internalModelId": "b9854a5c92dc496b997d99d20",
|
||||
"promptName": null,
|
||||
"promptVersion": null,
|
||||
"latency": 4794,
|
||||
"timeToFirstToken": null,
|
||||
"inputCost": 0.0000775,
|
||||
"outputCost": 0.00096,
|
||||
"totalCost": 0.0010375,
|
||||
"inputUsage": 31,
|
||||
"outputUsage": 96,
|
||||
"totalUsage": 127,
|
||||
"input": [
|
||||
{
|
||||
"content": "You are a helpful assistant that answers questions clearly and concisely.",
|
||||
"role": "system",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.system.message"
|
||||
},
|
||||
{
|
||||
"content": "What is Langfuse?",
|
||||
"role": "user",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.user.message"
|
||||
}
|
||||
],
|
||||
"output": {
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Langfuse is a tool designed for observability and monitoring of applications that utilize large language models (LLMs). It provides features to trace, log, and visualize requests, helping developers understand the behavior of their LLM-powered applications more effectively. This can be particularly useful for debugging, performance optimization, and ensuring reliable operation of systems that depend on LLMs. Langfuse is designed to integrate easily with existing infrastructure, supporting both self-hosted implementations and cloud-based solutions."
|
||||
},
|
||||
"gen_ai.system": "openai",
|
||||
"event.name": "gen_ai.choice"
|
||||
},
|
||||
"metadata": {
|
||||
"attributes": {
|
||||
"gen_ai.operation.name": "chat",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.request.model": "gpt-4o",
|
||||
"server.address": "api.openai.com",
|
||||
"model_request_parameters": {
|
||||
"function_tools": [],
|
||||
"allow_text_output": true,
|
||||
"output_tools": []
|
||||
},
|
||||
"gen_ai.usage.input_tokens": "31",
|
||||
"gen_ai.usage.output_tokens": "96",
|
||||
"gen_ai.response.model": "gpt-4o-2024-08-06",
|
||||
"events": [
|
||||
{
|
||||
"content": "You are a helpful assistant that answers questions clearly and concisely.",
|
||||
"role": "system",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.system.message"
|
||||
},
|
||||
{
|
||||
"content": "What is Langfuse?",
|
||||
"role": "user",
|
||||
"gen_ai.system": "openai",
|
||||
"gen_ai.message.index": 0,
|
||||
"event.name": "gen_ai.user.message"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Langfuse is a tool designed for observability and monitoring of applications that utilize large language models (LLMs). It provides features to trace, log, and visualize requests, helping developers understand the behavior of their LLM-powered applications more effectively. This can be particularly useful for debugging, performance optimization, and ensuring reliable operation of systems that depend on LLMs. Langfuse is designed to integrate easily with existing infrastructure, supporting both self-hosted implementations and cloud-based solutions."
|
||||
},
|
||||
"gen_ai.system": "openai",
|
||||
"event.name": "gen_ai.choice"
|
||||
}
|
||||
],
|
||||
"logfire.json_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"events": {
|
||||
"type": "array"
|
||||
},
|
||||
"model_request_parameters": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resourceAttributes": {
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
"telemetry.sdk.version": "1.33.1",
|
||||
"service.name": "unknown_service"
|
||||
},
|
||||
"scope": {
|
||||
"name": "pydantic-ai",
|
||||
"version": "0.2.15",
|
||||
"attributes": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -400,6 +400,177 @@ export const SEED_CHAT_ML_PROMPTS = [
|
||||
];
|
||||
|
||||
export const SEED_PROMPT_VERSIONS = [
|
||||
{
|
||||
createdBy: "user-1",
|
||||
type: "chat",
|
||||
prompt: [
|
||||
{
|
||||
role: "system",
|
||||
content: `## Role
|
||||
You are a Langfuse filter generator. Your sole function is to parse user queries about AI traces and output the corresponding filter array in JSON format. Map natural language to appropriate column names, operators, and values.
|
||||
|
||||
## Available columns and their types:
|
||||
- bookmarked (boolean): Starred/bookmarked traces
|
||||
- id (string): Trace ID
|
||||
- name (stringOptions): Trace name
|
||||
- environment (stringOptions): Environment (dev, prod, etc.)
|
||||
- timestamp (datetime): When the trace occurred
|
||||
- userId (string): User identifier
|
||||
- sessionId (string): Session identifier
|
||||
- metadata (stringObject): Custom metadata
|
||||
- version (string): Version identifier
|
||||
- release (string): Release identifier
|
||||
- level (stringOptions): Log level - DEBUG, DEFAULT, WARNING, ERROR
|
||||
- tags (arrayOptions): Custom tags
|
||||
- inputTokens (number): Input token count
|
||||
- outputTokens (number): Output token count
|
||||
- totalTokens (number): Total token count
|
||||
- tokens (number): Alias for total tokens
|
||||
- errorCount (number): Count of error-level observations
|
||||
- warningCount (number): Count of warning-level observations
|
||||
- defaultCount (number): Count of default-level observations
|
||||
- debugCount (number): Count of debug-level observations
|
||||
- scores_avg (numberObject): Numeric evaluation scores
|
||||
- score_categories (categoryOptions): Categorical evaluation scores
|
||||
- latency (number): Latency in seconds
|
||||
- inputCost (number): Input cost in USD
|
||||
- outputCost (number): Output cost in USD
|
||||
- totalCost (number): Total cost in USD
|
||||
|
||||
## Type-Operator Compatibility Rules
|
||||
|
||||
**string type** - Use for text matching operations:
|
||||
- Operators: "=", "contains", "does not contain", "starts with", "ends with"
|
||||
- Value: Always a single string
|
||||
- Example: {"type": "string", "column": "userId", "operator": "contains", "value": "john"}
|
||||
|
||||
**stringOptions type** - Use ONLY for selecting from predefined options:
|
||||
- Operators: "any of", "none of" ONLY
|
||||
- Value: Always an array of strings
|
||||
- Use when filtering columns like name, environment, level with multiple possible values
|
||||
- Example: {"type": "stringOptions", "column": "environment", "operator": "any of", "value": ["dev", "staging"]}
|
||||
|
||||
**arrayOptions type** - Use for tag filtering:
|
||||
- Operators: "any of", "none of", "all of"
|
||||
- Value: Always an array of strings
|
||||
- Example: {"type": "arrayOptions", "column": "tags", "operator": "any of", "value": ["important", "bug"]}
|
||||
|
||||
**number type** - Use for numeric comparisons:
|
||||
- Operators: "=", ">", "<", ">=", "<="
|
||||
- Value: Always a number
|
||||
- Example: {"type": "number", "column": "latency", "operator": ">", "value": 2.5}
|
||||
|
||||
**datetime type** - Use for time-based filtering:
|
||||
- Operators: ">", "<", ">=", "<="
|
||||
- Value: Always a date string
|
||||
- Example: {"type": "datetime", "column": "timestamp", "operator": ">", "value": "2024-01-01T00:00:00Z"}
|
||||
|
||||
**boolean type** - Use for true/false values:
|
||||
- Operators: "=", "<>"
|
||||
- Value: Always true or false
|
||||
- Example: {"type": "boolean", "column": "bookmarked", "operator": "=", "value": true}
|
||||
|
||||
**stringObject type** - Use for metadata key-value searches:
|
||||
- Operators: "=", "contains", "does not contain", "starts with", "ends with"
|
||||
- Value: Always a string
|
||||
- Requires "key" field for the metadata key
|
||||
- Example: {"type": "stringObject", "column": "metadata", "key": "userId", "operator": "=", "value": "123"}
|
||||
|
||||
**numberObject type** - Use for numeric score searches:
|
||||
- Operators: "=", ">", "<", ">=", "<="
|
||||
- Value: Always a number
|
||||
- Requires "key" field for the score name
|
||||
- Example: {"type": "numberObject", "column": "scores_avg", "key": "quality", "operator": ">", "value": 0.8}
|
||||
|
||||
## Output Format
|
||||
|
||||
Please respond ONLY with valid JSON. Do not include any explanation or extra text.
|
||||
The response should look like this without the leading EOF and trailing EOF.
|
||||
|
||||
EOF
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"type": "stringOptions|number|string|categoryOptions",
|
||||
"value": "value or array",
|
||||
"column": "exact column name",
|
||||
"operator": "= | > | < | any of"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
## Intent Parsing Guidelines
|
||||
|
||||
**Temporal expressions:**
|
||||
- "today", "yesterday", "last week" → timestamp filters
|
||||
- "after 2pm", "before noon", "since Monday" → timestamp with appropriate operators
|
||||
- Relative times: "last 24 hours", "past 3 days" → calculate from current time
|
||||
|
||||
**Performance queries:**
|
||||
- "slow", "high latency", "taking too long" → latency > threshold
|
||||
- "expensive", "costly", "high cost" → totalCost > threshold
|
||||
- "many tokens", "token heavy" → totalTokens > threshold
|
||||
- "cheap", "fast", "quick" → use < operators
|
||||
|
||||
**Error/Quality queries:**
|
||||
- "errors", "failed", "broken" → level = ERROR or errorCount > 0
|
||||
- "warnings" → level = WARNING or warningCount > 0
|
||||
- "successful", "working" → level = DEFAULT or errorCount = 0
|
||||
|
||||
**User/Session queries:**
|
||||
- "user john", "by user", "user ID" → userId filters
|
||||
- "session abc", "in session" → sessionId filters
|
||||
|
||||
**Environment queries:**
|
||||
- "prod", "production" → environment = production
|
||||
- "dev", "development", "staging" → environment matching
|
||||
- "live", "deployed" → typically production environment
|
||||
|
||||
**Metadata/Tags:**
|
||||
- "tagged with", "has tag" → tags contains
|
||||
- "metadata contains", "custom field" → metadata object queries
|
||||
|
||||
**Comparison operators:**
|
||||
- "more than", "over", "above", "greater" → >
|
||||
- "less than", "under", "below", "fewer" → <
|
||||
- "at least", "minimum" → >=
|
||||
- "at most", "maximum" → <=
|
||||
- "exactly", "equal to" → =
|
||||
- "not", "except", "excluding" → not_equals or not_contains
|
||||
|
||||
**Text search operations:**
|
||||
- "contains", "includes", "has" → use "string" type with "contains" operator
|
||||
- "equals", "is exactly" → use "string" type with "=" operator
|
||||
- "starts with", "begins with" → use "string" type with "starts with" operator
|
||||
|
||||
**Multi-option selections:**
|
||||
- "environment is dev or staging" → use "stringOptions" type with "any of" operator
|
||||
- "name is one of X, Y, Z" → use "stringOptions" type with "any of" operator
|
||||
- "level is ERROR or WARNING" → use "stringOptions" type with "any of" operator
|
||||
|
||||
## Current DateTime
|
||||
|
||||
This is the current datetime: {{currentDatetime}}
|
||||
Use it as a reference for datetime based queries when needed.
|
||||
|
||||
## Examples
|
||||
|
||||
Input: "I want to see all dev traces"
|
||||
Output: {"filters":[{"type":"stringOptions","value":["development","dev"],"column":"environment","operator":"any of"}]}
|
||||
|
||||
Input: "Show traces with version starting with v2"
|
||||
Output: {"filters":[{"type":"string","value":"v2","column":"version","operator":"starts with"}]}`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "{{userPrompt}}",
|
||||
},
|
||||
],
|
||||
name: "get-filter-conditions-from-query",
|
||||
version: 1,
|
||||
labels: ["production", "latest"],
|
||||
},
|
||||
{
|
||||
createdBy: "user-1",
|
||||
prompt: "Prompt 4 version 1 content with {{variable}}",
|
||||
|
||||
@@ -11,9 +11,8 @@ export const generateDatasetItemId = (
|
||||
datasetName: string,
|
||||
itemIndex: number,
|
||||
projectId: string,
|
||||
runNumber: number,
|
||||
) => {
|
||||
return `dataset-item-${datasetName}-${itemIndex}-${projectId.slice(-8)}-${runNumber}`;
|
||||
return `dataset-item-${datasetName}-${itemIndex}-${projectId.slice(-8)}-0`;
|
||||
};
|
||||
|
||||
export const generateDatasetRunTraceId = (
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import { FileContent, SeederOptions } from "./types";
|
||||
import { DataGenerator } from "./data-generators";
|
||||
import { ClickHouseQueryBuilder } from "./clickhouse-builder";
|
||||
import { FrameworkTraceLoader } from "./framework-traces/framework-trace-loader";
|
||||
import { EVAL_TRACE_COUNT, SEED_DATASETS } from "./postgres-seed-constants";
|
||||
import {
|
||||
clickhouseClient,
|
||||
DatasetRunItemRecordInsertType,
|
||||
logger,
|
||||
ObservationRecordInsertType,
|
||||
ScoreRecordInsertType,
|
||||
TraceRecordInsertType,
|
||||
} from "../../../src/server";
|
||||
import path from "path";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const DATASET_SCORE_NAMES = ["score-1", "score-2", "score-3"];
|
||||
const DATASET_RUN_SCORE_NAMES = [
|
||||
"dataset-run-score-1",
|
||||
"dataset-run-score-2",
|
||||
"dataset-run-score-3",
|
||||
];
|
||||
|
||||
/**
|
||||
* Orchestrates seeding operations across ClickHouse and PostgreSQL.
|
||||
*
|
||||
@@ -92,25 +102,26 @@ export class SeederOrchestrator {
|
||||
logger.info(
|
||||
`Processing run ${runNumber + 1}/${numberOfRuns} for project ${projectId}`,
|
||||
);
|
||||
// const now = Date.now();
|
||||
const now = Date.now();
|
||||
|
||||
const traces: TraceRecordInsertType[] = [];
|
||||
const observations: ObservationRecordInsertType[] = [];
|
||||
// const datasetRunItems: DatasetRunItemRecordInsertType[] = [];
|
||||
const datasetRunItems: DatasetRunItemRecordInsertType[] = [];
|
||||
const scores: ScoreRecordInsertType[] = [];
|
||||
|
||||
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 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(
|
||||
@@ -135,16 +146,43 @@ export class SeederOrchestrator {
|
||||
projectId,
|
||||
);
|
||||
|
||||
// Generate score data
|
||||
const score = this.dataGenerator.generateDatasetScore(
|
||||
trace,
|
||||
{
|
||||
datasetName: seedDataset.name,
|
||||
itemIndex,
|
||||
item: datasetItem,
|
||||
runNumber,
|
||||
},
|
||||
projectId,
|
||||
DATASET_SCORE_NAMES,
|
||||
);
|
||||
|
||||
traces.push(trace);
|
||||
observations.push(observation);
|
||||
// datasetRunItems.push(datasetRunItem);
|
||||
datasetRunItems.push(datasetRunItem);
|
||||
scores.push(score);
|
||||
}
|
||||
|
||||
// create dataset run level scores
|
||||
const datasetRunScore = this.dataGenerator.generateDatasetRunScore(
|
||||
`${seedDataset.name}-${projectId.slice(-8)}`,
|
||||
{
|
||||
datasetName: seedDataset.name,
|
||||
runNumber,
|
||||
},
|
||||
projectId,
|
||||
DATASET_RUN_SCORE_NAMES,
|
||||
);
|
||||
scores.push(datasetRunScore);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.queryBuilder.executeTracesInsert(traces);
|
||||
await this.queryBuilder.executeObservationsInsert(observations);
|
||||
// await this.queryBuilder.executeDatasetRunItemsInsert(datasetRunItems);
|
||||
await this.queryBuilder.executeDatasetRunItemsInsert(datasetRunItems);
|
||||
await this.queryBuilder.executeScoresInsert(scores);
|
||||
} catch (error) {
|
||||
logger.error(`✗ Insert failed:`, error);
|
||||
throw error;
|
||||
@@ -284,6 +322,12 @@ export class SeederOrchestrator {
|
||||
// Create synthetic data
|
||||
await this.createSyntheticData(projectIds, opts);
|
||||
|
||||
// Create traces for a realistic chat session
|
||||
await this.createSupportChatSessionTraces(projectIds);
|
||||
|
||||
// create traces from real examples for each framework source
|
||||
await this.createFrameworkTraces(projectIds);
|
||||
|
||||
// Log completion statistics (commented out to reduce terminal noise)
|
||||
await this.logStatistics();
|
||||
|
||||
@@ -341,4 +385,57 @@ export class SeederOrchestrator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createSupportChatSessionTraces(projectIds: string[]): Promise<void> {
|
||||
logger.info(
|
||||
`Creating support chat session data for ${projectIds.length} projects.`,
|
||||
);
|
||||
|
||||
for (const projectId of projectIds) {
|
||||
logger.info(`Processing support chat session for project ${projectId}`);
|
||||
|
||||
// Generate data using the data generator
|
||||
const { traces, observations, scores } =
|
||||
this.dataGenerator.generateSupportChatSessionData(projectId);
|
||||
|
||||
try {
|
||||
await this.queryBuilder.executeTracesInsert(traces);
|
||||
await this.queryBuilder.executeObservationsInsert(observations);
|
||||
await this.queryBuilder.executeScoresInsert(scores);
|
||||
} catch (error) {
|
||||
logger.error(`✗ Support chat session insert failed:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create traces from real examples for each framework source
|
||||
// useful for testing rendering
|
||||
async createFrameworkTraces(projectIds: string[]): Promise<void> {
|
||||
logger.info(`Creating framework traces for ${projectIds.length} projects.`);
|
||||
|
||||
const loader = new FrameworkTraceLoader();
|
||||
|
||||
for (const projectId of projectIds) {
|
||||
logger.info(`Processing framework traces for project ${projectId}`);
|
||||
|
||||
const { traces, observations, scores } =
|
||||
loader.loadTracesForProject(projectId);
|
||||
|
||||
try {
|
||||
if (traces.length > 0) {
|
||||
await this.queryBuilder.executeTracesInsert(traces);
|
||||
}
|
||||
if (observations.length > 0) {
|
||||
await this.queryBuilder.executeObservationsInsert(observations);
|
||||
}
|
||||
if (scores.length > 0) {
|
||||
await this.queryBuilder.executeScoresInsert(scores);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`✗ Framework traces insert failed:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const RequestHeaderSchema = z.object({
|
||||
|
||||
export const WebhookActionConfigSchema = z.object({
|
||||
type: z.literal("WEBHOOK"),
|
||||
url: z.url(),
|
||||
url: z.url(), // Basic URL validation only - manual security validation called separately
|
||||
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
|
||||
|
||||
@@ -25,4 +25,14 @@ export const DatasetRunItemSchema = z.object({
|
||||
datasetItemMetadata: MetadataDomain,
|
||||
});
|
||||
|
||||
export type DatasetRunItemDomain = z.infer<typeof DatasetRunItemSchema>;
|
||||
// Conditional type for dataset run item domain with optional IO
|
||||
export type DatasetRunItemDomain<WithIO extends boolean = true> =
|
||||
WithIO extends true
|
||||
? z.infer<typeof DatasetRunItemSchema>
|
||||
: Omit<
|
||||
z.infer<typeof DatasetRunItemSchema>,
|
||||
| "datasetRunMetadata"
|
||||
| "datasetItemInput"
|
||||
| "datasetItemExpectedOutput"
|
||||
| "datasetItemMetadata"
|
||||
>;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import { z } from "zod/v4";
|
||||
import { isPresent } from "../utils/typeChecks";
|
||||
|
||||
// Category type, used for categorical and boolean configs
|
||||
export const ScoreConfigCategory = z.object({
|
||||
label: z.string().min(1),
|
||||
value: z.number(),
|
||||
});
|
||||
|
||||
// Numeric config fields
|
||||
export const NumericConfigFields = z.object({
|
||||
maxValue: z.number().nullish(),
|
||||
minValue: z.number().nullish(),
|
||||
dataType: z.literal("NUMERIC"),
|
||||
categories: z.undefined().nullish(),
|
||||
});
|
||||
|
||||
// Boolean config fields
|
||||
export const BooleanConfigFields = z.object({
|
||||
dataType: z.literal("BOOLEAN"),
|
||||
maxValue: z.number().nullish(),
|
||||
minValue: z.number().nullish(),
|
||||
categories: z
|
||||
.array(ScoreConfigCategory)
|
||||
.length(2, "Boolean data type must have exactly 2 categories.")
|
||||
.refine((categories) => {
|
||||
const expectedCategories = [
|
||||
{ label: "True", value: 1 },
|
||||
{ label: "False", value: 0 },
|
||||
];
|
||||
return categories.every(
|
||||
(category, index) =>
|
||||
category.label === expectedCategories[index].label &&
|
||||
category.value === expectedCategories[index].value,
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
||||
// Category config fields and types
|
||||
export const validateCategories = (
|
||||
categories: z.infer<typeof ScoreConfigCategory>[],
|
||||
ctx: z.RefinementCtx,
|
||||
) => {
|
||||
const uniqueNames = new Set<string>();
|
||||
const uniqueValues = new Set<number>();
|
||||
|
||||
for (const category of categories) {
|
||||
if (uniqueNames.has(category.label)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Duplicate category label: ${category.label}, category labels must be unique`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
uniqueNames.add(category.label);
|
||||
|
||||
if (uniqueValues.has(category.value)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Duplicate category value: ${category.value}, category values must be unique`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
uniqueValues.add(category.value);
|
||||
}
|
||||
};
|
||||
|
||||
export const CategoricalConfigFields = z.object({
|
||||
maxValue: z.undefined().nullish(),
|
||||
minValue: z.undefined().nullish(),
|
||||
dataType: z.literal("CATEGORICAL"),
|
||||
categories: z.array(ScoreConfigCategory).superRefine(validateCategories),
|
||||
});
|
||||
|
||||
const ScoreConfigBase = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().min(1).max(35),
|
||||
isArchived: z.boolean(),
|
||||
description: z.string().nullish(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
export const validateNumericRangeFields = (
|
||||
data: Pick<ScoreConfigDomain, "maxValue" | "minValue" | "dataType">,
|
||||
ctx: z.RefinementCtx,
|
||||
): void | Promise<void> => {
|
||||
if (data.dataType === "NUMERIC") {
|
||||
if (
|
||||
isPresent(data.maxValue) &&
|
||||
isPresent(data.minValue) &&
|
||||
data.maxValue <= data.minValue
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Maximum value must be greater than Minimum value",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ScoreConfigSchema = z
|
||||
.discriminatedUnion("dataType", [
|
||||
z.object({
|
||||
...ScoreConfigBase.shape,
|
||||
...NumericConfigFields.shape,
|
||||
}),
|
||||
z.object({
|
||||
...ScoreConfigBase.shape,
|
||||
...CategoricalConfigFields.shape,
|
||||
}),
|
||||
z.object({
|
||||
...ScoreConfigBase.shape,
|
||||
...BooleanConfigFields.shape,
|
||||
}),
|
||||
])
|
||||
.superRefine(validateNumericRangeFields);
|
||||
|
||||
export type ScoreConfigDomain = z.infer<typeof ScoreConfigSchema>;
|
||||
export type ScoreConfigCategoryDomain = z.infer<typeof ScoreConfigCategory>;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ScoreDataType } from "@prisma/client";
|
||||
import z from "zod/v4";
|
||||
import { MetadataDomain } from "./traces";
|
||||
import { ScoreDataType } from "@prisma/client";
|
||||
|
||||
export const ScoreSource = {
|
||||
ANNOTATION: "ANNOTATION",
|
||||
|
||||
+25
-42
@@ -38,6 +38,7 @@ const EnvSchema = z.object({
|
||||
LANGFUSE_CACHE_PROMPT_ENABLED: z.enum(["true", "false"]).default("true"),
|
||||
LANGFUSE_CACHE_PROMPT_TTL_SECONDS: z.coerce.number().default(300), // 5 minutes
|
||||
CLICKHOUSE_URL: z.string().url(),
|
||||
CLICKHOUSE_READ_ONLY_URL: z.string().url().optional(),
|
||||
CLICKHOUSE_CLUSTER_NAME: z.string().default("default"),
|
||||
CLICKHOUSE_DB: z.string().default("default"),
|
||||
CLICKHOUSE_USER: z.string(),
|
||||
@@ -50,6 +51,10 @@ const EnvSchema = z.object({
|
||||
.nonnegative()
|
||||
.default(15_000),
|
||||
LANGFUSE_INGESTION_QUEUE_SHARD_COUNT: z.coerce.number().positive().default(1),
|
||||
LANGFUSE_OTEL_INGESTION_QUEUE_SHARD_COUNT: z.coerce
|
||||
.number()
|
||||
.positive()
|
||||
.default(1),
|
||||
LANGFUSE_TRACE_UPSERT_QUEUE_SHARD_COUNT: z.coerce
|
||||
.number()
|
||||
.positive()
|
||||
@@ -126,48 +131,6 @@ const EnvSchema = z.object({
|
||||
LANGFUSE_CLICKHOUSE_DELETION_TIMEOUT_MS: z.coerce.number().default(600_000), // 10 minutes
|
||||
LANGFUSE_CLICKHOUSE_QUERY_MAX_ATTEMPTS: z.coerce.number().default(3), // Maximum attempts for socket hang up errors
|
||||
LANGFUSE_SKIP_S3_LIST_FOR_OBSERVATIONS_PROJECT_IDS: z.string().optional(),
|
||||
// Dataset Run Items Migration Environment Variables
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_WRITE_CH: z
|
||||
.enum(["true", "false"])
|
||||
.default("true"),
|
||||
LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_READ_CH: z
|
||||
.enum(["true", "false"])
|
||||
.default("true"),
|
||||
LANGFUSE_EXPERIMENT_COMPARE_READ_FROM_AGGREGATING_MERGE_TREES: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_EXPERIMENT_ADD_QUERY_RESULT_TO_SPAN_PROJECT_IDS: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) =>
|
||||
s ? s.split(",").map((s) => s.toLowerCase().trim()) : [],
|
||||
),
|
||||
LANGFUSE_EXPERIMENT_SAMPLING_RATE: z.coerce
|
||||
.number()
|
||||
.min(0)
|
||||
.max(1)
|
||||
.default(0.1),
|
||||
LANGFUSE_EXPERIMENT_WHITELISTED_PROJECT_IDS: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) =>
|
||||
s ? s.split(",").map((s) => s.toLowerCase().trim()) : [],
|
||||
),
|
||||
LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT_SHORT_TERM: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_EXPERIMENT_INSERT_INTO_AGGREGATING_MERGE_TREES: z
|
||||
.enum(["true", "false"])
|
||||
.default("false"),
|
||||
LANGFUSE_EXPERIMENT_WHITELISTED_AMT_TABLES: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) =>
|
||||
s ? s.split(",").map((s) => s.toLowerCase().trim()) : [],
|
||||
),
|
||||
LANGFUSE_INGESTION_PROCESSING_SAMPLED_PROJECTS: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -200,6 +163,12 @@ const EnvSchema = z.object({
|
||||
return new Map<string, number>();
|
||||
}
|
||||
}),
|
||||
LANGFUSE_WEBHOOK_WHITELISTED_IPS: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) =>
|
||||
s ? s.split(",").map((s) => s.toLowerCase().trim()) : [],
|
||||
),
|
||||
SLACK_CLIENT_ID: z.string().optional(),
|
||||
SLACK_CLIENT_SECRET: z.string().optional(),
|
||||
SLACK_STATE_SECRET: z.string().optional(),
|
||||
@@ -224,6 +193,20 @@ const EnvSchema = z.object({
|
||||
.int()
|
||||
.positive()
|
||||
.default(600_000), // 10 minutes
|
||||
|
||||
LANGFUSE_FETCH_LLM_COMPLETION_TIMEOUT_MS: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.default(120_000), // 2 minutes
|
||||
|
||||
LANGFUSE_AWS_BEDROCK_REGION: z.string().optional(),
|
||||
|
||||
// Langfuse AI Features
|
||||
LANGFUSE_AI_FEATURES_PUBLIC_KEY: z.string().optional(),
|
||||
LANGFUSE_AI_FEATURES_SECRET_KEY: z.string().optional(),
|
||||
LANGFUSE_AI_FEATURES_HOST: z.string().optional(),
|
||||
LANGFUSE_AI_FEATURES_PROJECT_ID: z.string().optional(),
|
||||
});
|
||||
|
||||
export const env: z.infer<typeof EnvSchema> =
|
||||
|
||||
@@ -7,4 +7,5 @@ export const QUEUE_ERROR_MESSAGES = {
|
||||
TOO_LOW_MAX_TOKENS_ERROR: "Error: Unterminated string in JSON at position",
|
||||
OUTPUT_TOKENS_TOO_LONG_ERROR:
|
||||
"Could not parse response content as the length limit was reached",
|
||||
TIMEOUT_ERROR: "Request timed out",
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./validation";
|
||||
@@ -0,0 +1,49 @@
|
||||
import { z } from "zod/v4";
|
||||
import { ScoreConfig as ScoreConfigDbType } from "@prisma/client";
|
||||
import {
|
||||
ScoreConfigDomain,
|
||||
ScoreConfigSchema,
|
||||
} from "../../domain/score-configs";
|
||||
|
||||
/**
|
||||
* Use this function when pulling a list of score configs from the database before using in the application to ensure type safety.
|
||||
* All score configs are expected to pass the validation. If a score fails validation, it will be logged to Otel.
|
||||
* @param scoreConfigs
|
||||
* @returns list of validated score configs
|
||||
*/
|
||||
export const filterAndValidateDbScoreConfigList = (
|
||||
scoreConfigs: ScoreConfigDbType[],
|
||||
onParseError?: (error: z.ZodError) => void, // eslint-disable-line no-unused-vars
|
||||
): ScoreConfigDomain[] =>
|
||||
scoreConfigs.reduce((acc, ts) => {
|
||||
const result = ScoreConfigSchema.safeParse(ts);
|
||||
if (result.success) {
|
||||
acc.push(result.data);
|
||||
} else {
|
||||
onParseError?.(result.error);
|
||||
}
|
||||
return acc;
|
||||
}, [] as ScoreConfigDomain[]);
|
||||
|
||||
/**
|
||||
* Use this function when pulling a single score config from the database before using in the application to ensure type safety.
|
||||
* The score is expected to pass the validation. If a score fails validation, an error will be thrown.
|
||||
* @param scoreConfig
|
||||
* @returns validated score config
|
||||
* @throws error if score fails validation
|
||||
*/
|
||||
export const validateDbScoreConfig = (
|
||||
scoreConfig: ScoreConfigDbType,
|
||||
): ScoreConfigDomain => ScoreConfigSchema.parse(scoreConfig);
|
||||
|
||||
/**
|
||||
* Use this function when pulling a single score config from the database before using in the application to ensure type safety.
|
||||
* This function will NOT throw an error by default. The score is expected to pass the validation.
|
||||
* @param scoreConfig
|
||||
* @returns score config validation object:
|
||||
* - success: true if the score config passes validation
|
||||
* - data: the validated score config if success is true
|
||||
* - error: the error object if success is false
|
||||
*/
|
||||
export const validateDbScoreConfigSafe = (scoreConfig: ScoreConfigDbType) =>
|
||||
ScoreConfigSchema.safeParse(scoreConfig);
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./interfaces";
|
||||
export * from "./scoreConfigTypes";
|
||||
|
||||
@@ -8,7 +8,9 @@ export const GetScoreQueryV2 = GetScoreQuery;
|
||||
export const GetScoreResponseV2 = APIScoreSchemaV2;
|
||||
|
||||
// GET /scores v2
|
||||
export const GetScoresQueryV2 = GetScoresQuery;
|
||||
export const GetScoresQueryV2 = GetScoresQuery.extend({
|
||||
sessionId: z.string().nullish(),
|
||||
});
|
||||
export const GetScoreResponseDataV2 = z.intersection(
|
||||
APIScoreSchemaV2,
|
||||
z.object({
|
||||
|
||||
@@ -2,7 +2,7 @@ import z from "zod/v4";
|
||||
import { applyScoreValidation } from "../../../../utils/scores";
|
||||
import { PostScoreBodyFoundationSchema } from "../shared";
|
||||
import { isPresent } from "../../../../utils/typeChecks";
|
||||
import { Category as ConfigCategory } from "../../scoreConfigTypes";
|
||||
import { ScoreConfigCategory } from "../../../../domain/score-configs";
|
||||
|
||||
export const ScoreBodyWithoutConfig = applyScoreValidation(
|
||||
z.discriminatedUnion("dataType", [
|
||||
@@ -54,7 +54,7 @@ const ScorePropsAgainstConfigNumeric = z
|
||||
const ScorePropsAgainstConfigCategorical = z
|
||||
.object({
|
||||
value: z.string(),
|
||||
categories: z.array(ConfigCategory),
|
||||
categories: z.array(ScoreConfigCategory),
|
||||
dataType: z.literal("CATEGORICAL"),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
|
||||
@@ -19,10 +19,9 @@ export type NumericAggregate = BaseAggregate & {
|
||||
average: number;
|
||||
};
|
||||
|
||||
export type ScoreAggregate = Record<
|
||||
string,
|
||||
CategoricalAggregate | NumericAggregate
|
||||
>;
|
||||
export type AggregatedScoreData = CategoricalAggregate | NumericAggregate;
|
||||
|
||||
export type ScoreAggregate = Record<string, AggregatedScoreData>;
|
||||
|
||||
export type ScoreSimplified = {
|
||||
id: string;
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { ScoreConfig as ScoreConfigDbType } from "@prisma/client";
|
||||
|
||||
import { isPresent } from "../../utils/typeChecks";
|
||||
import {
|
||||
jsonSchema,
|
||||
paginationMetaResponseZod,
|
||||
publicApiPaginationZod,
|
||||
} from "../../utils/zod";
|
||||
|
||||
/**
|
||||
* Types to use across codebase
|
||||
*/
|
||||
export type ConfigCategory = z.infer<typeof Category>;
|
||||
export type ValidatedScoreConfig = z.infer<typeof ValidatedScoreConfigSchema>;
|
||||
|
||||
const validateCategories = (
|
||||
categories: ConfigCategory[],
|
||||
ctx: z.RefinementCtx,
|
||||
) => {
|
||||
const uniqueNames = new Set<string>();
|
||||
const uniqueValues = new Set<number>();
|
||||
|
||||
for (const category of categories) {
|
||||
if (uniqueNames.has(category.label)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Duplicate category label: ${category.label}, category labels must be unique`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
uniqueNames.add(category.label);
|
||||
|
||||
if (uniqueValues.has(category.value)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Duplicate category value: ${category.value}, category values must be unique`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
uniqueValues.add(category.value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Objects
|
||||
*/
|
||||
export const Category = z.object({
|
||||
label: z.string().min(1),
|
||||
value: z.number(),
|
||||
});
|
||||
|
||||
const Categories = z.array(Category);
|
||||
|
||||
const NumericScoreConfig = z.object({
|
||||
maxValue: z.number().nullish(),
|
||||
minValue: z.number().nullish(),
|
||||
dataType: z.literal("NUMERIC"),
|
||||
categories: z.undefined().nullish(),
|
||||
});
|
||||
|
||||
const CategoricalScoreConfig = z.object({
|
||||
maxValue: z.undefined().nullish(),
|
||||
minValue: z.undefined().nullish(),
|
||||
dataType: z.literal("CATEGORICAL"),
|
||||
categories: jsonSchema.superRefine((categories, ctx) => {
|
||||
const parseResult = Categories.safeParse(categories);
|
||||
if (!parseResult.success) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Category must be an array of objects with label value pairs, where labels and values are unique.",
|
||||
} as z.core.$ZodIssueCustom);
|
||||
return;
|
||||
}
|
||||
|
||||
validateCategories(categories as ConfigCategory[], ctx);
|
||||
}),
|
||||
});
|
||||
|
||||
const BooleanScoreConfig = z.object({
|
||||
maxValue: z.undefined().nullish(),
|
||||
minValue: z.undefined().nullish(),
|
||||
dataType: z.literal("BOOLEAN"),
|
||||
categories: z
|
||||
.array(Category)
|
||||
.length(2, "Boolean data type must have exactly 2 categories.")
|
||||
.refine((categories) => {
|
||||
const expectedCategories = [
|
||||
{ label: "True", value: 1 },
|
||||
{ label: "False", value: 0 },
|
||||
];
|
||||
return categories.every(
|
||||
(category, index) =>
|
||||
category.label === expectedCategories[index].label &&
|
||||
category.value === expectedCategories[index].value,
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
||||
const ScoreConfigBase = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().min(1).max(35),
|
||||
isArchived: z.boolean(),
|
||||
description: z.string().nullish(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
const ScoreConfigPostBase = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
const ValidatedScoreConfigSchema = z
|
||||
.union([
|
||||
ScoreConfigBase.merge(NumericScoreConfig),
|
||||
ScoreConfigBase.merge(
|
||||
z.object({
|
||||
maxValue: z.undefined().nullish(),
|
||||
minValue: z.undefined().nullish(),
|
||||
dataType: z.literal("CATEGORICAL"),
|
||||
categories: Categories.superRefine(validateCategories),
|
||||
}),
|
||||
),
|
||||
ScoreConfigBase.merge(BooleanScoreConfig),
|
||||
])
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.dataType === "NUMERIC") {
|
||||
if (
|
||||
isPresent(data.maxValue) &&
|
||||
isPresent(data.minValue) &&
|
||||
data.maxValue <= data.minValue
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Maximum value must be greater than Minimum value",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Use this function when pulling a list of score configs from the database before using in the application to ensure type safety.
|
||||
* All score configs are expected to pass the validation. If a score fails validation, it will be logged to Otel.
|
||||
* @param scoreConfigs
|
||||
* @returns list of validated score configs
|
||||
*/
|
||||
export const filterAndValidateDbScoreConfigList = (
|
||||
scoreConfigs: ScoreConfigDbType[],
|
||||
onParseError?: (error: z.ZodError) => void, // eslint-disable-line no-unused-vars
|
||||
): ValidatedScoreConfig[] =>
|
||||
scoreConfigs.reduce((acc, ts) => {
|
||||
const result = ValidatedScoreConfigSchema.safeParse(ts);
|
||||
if (result.success) {
|
||||
acc.push(result.data);
|
||||
} else {
|
||||
onParseError?.(result.error);
|
||||
}
|
||||
return acc;
|
||||
}, [] as ValidatedScoreConfig[]);
|
||||
|
||||
/**
|
||||
* Use this function when pulling a single score config from the database before using in the application to ensure type safety.
|
||||
* The score is expected to pass the validation. If a score fails validation, an error will be thrown.
|
||||
* @param scoreConfig
|
||||
* @returns validated score config
|
||||
* @throws error if score fails validation
|
||||
*/
|
||||
export const validateDbScoreConfig = (
|
||||
scoreConfig: ScoreConfigDbType,
|
||||
): ValidatedScoreConfig => ValidatedScoreConfigSchema.parse(scoreConfig);
|
||||
|
||||
/**
|
||||
* Use this function when pulling a single score config from the database before using in the application to ensure type safety.
|
||||
* This function will NOT throw an error by default. The score is expected to pass the validation.
|
||||
* @param scoreConfig
|
||||
* @returns score config validation object:
|
||||
* - success: true if the score config passes validation
|
||||
* - data: the validated score config if success is true
|
||||
* - error: the error object if success is false
|
||||
*/
|
||||
export const validateDbScoreConfigSafe = (scoreConfig: ScoreConfigDbType) =>
|
||||
ValidatedScoreConfigSchema.safeParse(scoreConfig);
|
||||
|
||||
/**
|
||||
* Endpoints
|
||||
*/
|
||||
|
||||
// GET /score-configs/{configId}
|
||||
export const GetScoreConfigQuery = z.object({
|
||||
configId: z.string(),
|
||||
});
|
||||
|
||||
export const GetScoreConfigResponse = ValidatedScoreConfigSchema;
|
||||
|
||||
// POST /score-configs
|
||||
export const PostScoreConfigBody = z
|
||||
.discriminatedUnion("dataType", [
|
||||
ScoreConfigPostBase.merge(CategoricalScoreConfig),
|
||||
ScoreConfigPostBase.merge(NumericScoreConfig),
|
||||
ScoreConfigPostBase.merge(
|
||||
z.object({
|
||||
dataType: z.literal("BOOLEAN"),
|
||||
categories: z.undefined().nullish(),
|
||||
}),
|
||||
),
|
||||
])
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.dataType === "NUMERIC") {
|
||||
if (
|
||||
isPresent(data.maxValue) &&
|
||||
isPresent(data.minValue) &&
|
||||
data.maxValue <= data.minValue
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Maximum value must be greater than Minimum value",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const PostScoreConfigResponse = ValidatedScoreConfigSchema;
|
||||
|
||||
// GET /score-configs
|
||||
export const GetScoreConfigsQuery = z.object({
|
||||
...publicApiPaginationZod,
|
||||
});
|
||||
|
||||
export const GetScoreConfigsResponse = z.object({
|
||||
data: z.array(ValidatedScoreConfigSchema),
|
||||
meta: paginationMetaResponseZod,
|
||||
});
|
||||
@@ -19,6 +19,7 @@ export * from "./interfaces/rate-limits";
|
||||
export * from "./tableDefinitions/typeHelpers";
|
||||
export * from "./domain/webhooks";
|
||||
export * from "./domain/dataset-run-items";
|
||||
export * from "./domain/score-configs";
|
||||
|
||||
// llm api
|
||||
export * from "./server/llm/types";
|
||||
@@ -37,6 +38,9 @@ export * from "./features/annotation/types";
|
||||
// scores
|
||||
export * from "./features/scores";
|
||||
|
||||
// score configs
|
||||
export * from "./features/scoreConfigs";
|
||||
|
||||
// comments
|
||||
export * from "./features/comments/types";
|
||||
|
||||
|
||||
@@ -13,26 +13,18 @@ export const CloudConfigSchema = z.object({
|
||||
customerId: z.string().optional(),
|
||||
activeSubscriptionId: z.string().optional(),
|
||||
activeProductId: z.string().optional(),
|
||||
activeUsageProductId: z.string().optional(),
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
isLegacySubscription:
|
||||
data?.activeProductId !== undefined &&
|
||||
data?.activeUsageProductId === undefined,
|
||||
}))
|
||||
.optional(),
|
||||
|
||||
// custom rate limits for an organization
|
||||
rateLimitOverrides: CloudConfigRateLimit.optional(),
|
||||
|
||||
// billing alert configuration
|
||||
usageAlerts: z
|
||||
.object({
|
||||
enabled: z.boolean().default(true),
|
||||
type: z.enum(["STRIPE"]).default("STRIPE"),
|
||||
threshold: z.number().int().positive(),
|
||||
alertId: z.string(), // Alert ID for tracking
|
||||
meterId: z.string(), // Meter ID for usage tracking
|
||||
notifications: z.object({
|
||||
email: z.boolean().default(true),
|
||||
recipients: z.array(z.string().email()).default([]),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type CloudConfigSchema = z.infer<typeof CloudConfigSchema>;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { type Organization } from "@prisma/client";
|
||||
import { CloudConfigSchema } from "./cloudConfigSchema";
|
||||
|
||||
type parsedOrg = Omit<Organization, "cloudConfig"> & {
|
||||
export type ParsedOrganization = Omit<Organization, "cloudConfig"> & {
|
||||
cloudConfig: CloudConfigSchema | null;
|
||||
};
|
||||
|
||||
export function parseDbOrg(dbOrg: Organization): parsedOrg {
|
||||
export function parseDbOrg(dbOrg: Organization): ParsedOrganization {
|
||||
const { cloudConfig, ...org } = dbOrg;
|
||||
|
||||
const parsedCloudConfig = CloudConfigSchema.safeParse(cloudConfig);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { prisma } from "../../db";
|
||||
import { redis, safeMultiDel } from "..";
|
||||
import { logger } from "../logger";
|
||||
|
||||
import { type ApiKey } from "../../db";
|
||||
|
||||
/**
|
||||
* Invalidate cached API keys from Redis cache
|
||||
*
|
||||
* Utility used by higher-level helpers to remove individual API keys from the cache,
|
||||
* e.g. after key rotation, revocation, or entitlement/plan changes.
|
||||
*
|
||||
* Note: This only invalidates the Redis cache, not the API keys themselves in the database.
|
||||
*
|
||||
* Behavior:
|
||||
* - Skips keys without a `fastHashedSecretKey`
|
||||
* - No-ops when Redis is not configured
|
||||
*
|
||||
* @param apiKeys - List of API key records to invalidate from cache
|
||||
* @param identifier - Context string for logging (e.g., org or project identifier)
|
||||
*/
|
||||
export async function invalidateCachedApiKeys(
|
||||
apiKeys: ApiKey[],
|
||||
identifier: string,
|
||||
) {
|
||||
const hashKeys = apiKeys.map((key) => key.fastHashedSecretKey);
|
||||
|
||||
const filteredHashKeys = hashKeys.filter((hash): hash is string =>
|
||||
Boolean(hash),
|
||||
);
|
||||
if (filteredHashKeys.length === 0) {
|
||||
logger.info("No valid keys to invalidate");
|
||||
return;
|
||||
}
|
||||
|
||||
if (redis) {
|
||||
logger.info(`Invalidating API keys in redis for ${identifier}`);
|
||||
const keysToDelete = filteredHashKeys.map((hash) => `api-key:${hash}`);
|
||||
await safeMultiDel(redis, keysToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all cached API keys for an organization from Redis cache
|
||||
*
|
||||
* This function is used when organization-level changes occur that affect API key validity,
|
||||
* such as:
|
||||
* - Plan changes (subscription created/updated/deleted)
|
||||
* - Usage threshold state changes (blocking/unblocking)
|
||||
* - Billing cycle changes
|
||||
*
|
||||
* Note: This only invalidates the Redis cache, not the API keys themselves in the database.
|
||||
*
|
||||
* @param orgId - The organization ID whose API keys should be invalidated from cache
|
||||
*/
|
||||
export async function invalidateCachedOrgApiKeys(orgId: string): Promise<void> {
|
||||
const apiKeys = await prisma.apiKey.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
project: {
|
||||
orgId,
|
||||
},
|
||||
},
|
||||
{ orgId },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const hashKeys = apiKeys
|
||||
.map((key) => key.fastHashedSecretKey)
|
||||
.filter((hash): hash is string => Boolean(hash));
|
||||
|
||||
if (hashKeys.length === 0) {
|
||||
logger.info(`No valid API keys to invalidate for org ${orgId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (redis) {
|
||||
logger.info(`Invalidating API keys in redis for org ${orgId}`);
|
||||
const keysToDelete = hashKeys.map((hash) => `api-key:${hash}`);
|
||||
await safeMultiDel(redis, keysToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all cached API keys for a project from Redis cache
|
||||
*
|
||||
* This function is used when project-level changes occur that affect API key validity.
|
||||
*
|
||||
* Note: This only invalidates the Redis cache, not the API keys themselves in the database.
|
||||
*
|
||||
* @param projectId - The project ID whose API keys should be invalidated from cache
|
||||
*/
|
||||
export async function invalidateCachedProjectApiKeys(
|
||||
projectId: string,
|
||||
): Promise<void> {
|
||||
const apiKeys = await prisma.apiKey.findMany({
|
||||
where: {
|
||||
projectId: projectId,
|
||||
scope: "PROJECT",
|
||||
},
|
||||
});
|
||||
|
||||
const hashKeys = apiKeys
|
||||
.map((key) => key.fastHashedSecretKey)
|
||||
.filter((hash): hash is string => Boolean(hash));
|
||||
|
||||
if (hashKeys.length === 0) {
|
||||
logger.info(`No valid API keys to invalidate for project ${projectId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (redis) {
|
||||
logger.info(`Invalidating API keys in redis for project ${projectId}`);
|
||||
const keysToDelete = hashKeys.map((hash) => `api-key:${hash}`);
|
||||
await safeMultiDel(redis, keysToDelete);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ const ApiKeyBaseSchema = z.object({
|
||||
orgId: z.string(),
|
||||
plan: z.enum(plans as unknown as [string, ...string[]]),
|
||||
rateLimitOverrides: CloudConfigRateLimit.nullish(),
|
||||
isIngestionSuspended: z.boolean().nullish(),
|
||||
});
|
||||
|
||||
export const OrgEnrichedApiKey = z.discriminatedUnion("scope", [
|
||||
@@ -64,6 +65,7 @@ type ApiAccessScopeMetadata = {
|
||||
rateLimitOverrides: z.infer<typeof CloudConfigRateLimit>;
|
||||
apiKeyId: string;
|
||||
publicKey: string;
|
||||
isIngestionSuspended: boolean | null | undefined;
|
||||
};
|
||||
|
||||
export type ApiAccessScopeIngestion = BaseApiAccessScope &
|
||||
|
||||
@@ -6,6 +6,8 @@ import { propagation, context } from "@opentelemetry/api";
|
||||
|
||||
export type ClickhouseClientType = ReturnType<typeof createClient>;
|
||||
|
||||
export type PreferredClickhouseService = "ReadWrite" | "ReadOnly";
|
||||
|
||||
/**
|
||||
* ClickHouseClientManager provides a singleton pattern for managing ClickHouse clients.
|
||||
* It creates and reuses clients based on their configuration to avoid creating
|
||||
@@ -35,22 +37,40 @@ export class ClickHouseClientManager {
|
||||
* @param opts Client parameters
|
||||
* @returns String hash key
|
||||
*/
|
||||
private generateClientSettingsKey(
|
||||
private generateClientSettings(
|
||||
opts: NodeClickHouseClientConfigOptions,
|
||||
): string {
|
||||
preferredClickhouseService: PreferredClickhouseService = "ReadWrite",
|
||||
): NodeClickHouseClientConfigOptions {
|
||||
const keyParams = {
|
||||
url: env.CLICKHOUSE_URL,
|
||||
url: this.getClickhouseUrl(preferredClickhouseService),
|
||||
username: env.CLICKHOUSE_USER,
|
||||
password: env.CLICKHOUSE_PASSWORD,
|
||||
database: env.CLICKHOUSE_DB,
|
||||
http_headers: opts?.http_headers,
|
||||
http_headers: opts?.http_headers ?? {},
|
||||
settings: opts?.clickhouse_settings,
|
||||
request_timeout: opts?.request_timeout ?? 30000,
|
||||
...(opts.request_timeout
|
||||
? { request_timeout: opts.request_timeout }
|
||||
: {}),
|
||||
|
||||
// Include any other relevant config options
|
||||
};
|
||||
return JSON.stringify(keyParams);
|
||||
return keyParams;
|
||||
}
|
||||
|
||||
private generateClientSettingsKey(
|
||||
settings: NodeClickHouseClientConfigOptions,
|
||||
): string {
|
||||
return JSON.stringify(settings);
|
||||
}
|
||||
|
||||
private getClickhouseUrl = (
|
||||
preferredClickhouseService: PreferredClickhouseService,
|
||||
) => {
|
||||
return preferredClickhouseService === "ReadWrite"
|
||||
? env.CLICKHOUSE_URL
|
||||
: env.CLICKHOUSE_READ_ONLY_URL || env.CLICKHOUSE_URL;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or create a client based on the provided parameters
|
||||
* @param opts Client configuration parameters
|
||||
@@ -58,13 +78,17 @@ export class ClickHouseClientManager {
|
||||
*/
|
||||
public getClient(
|
||||
opts: NodeClickHouseClientConfigOptions,
|
||||
preferredClickhouseService: PreferredClickhouseService = "ReadWrite",
|
||||
): ClickhouseClientType {
|
||||
const key = this.generateClientSettingsKey(opts);
|
||||
const settings = this.generateClientSettings(
|
||||
opts,
|
||||
preferredClickhouseService,
|
||||
);
|
||||
const key = this.generateClientSettingsKey(settings);
|
||||
if (!this.clientMap.has(key)) {
|
||||
const headers = opts?.http_headers ?? {};
|
||||
const activeSpan = getCurrentSpan();
|
||||
if (activeSpan) {
|
||||
propagation.inject(context.active(), headers);
|
||||
propagation.inject(context.active(), settings.http_headers);
|
||||
}
|
||||
|
||||
const cloudOptions: Record<string, unknown> = {};
|
||||
@@ -78,11 +102,7 @@ export class ClickHouseClientManager {
|
||||
|
||||
const client = createClient({
|
||||
...opts,
|
||||
url: env.CLICKHOUSE_URL,
|
||||
username: env.CLICKHOUSE_USER,
|
||||
password: env.CLICKHOUSE_PASSWORD,
|
||||
database: env.CLICKHOUSE_DB,
|
||||
http_headers: headers,
|
||||
...settings,
|
||||
keep_alive: {
|
||||
idle_socket_ttl: env.CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL,
|
||||
},
|
||||
@@ -99,9 +119,6 @@ export class ClickHouseClientManager {
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(opts.request_timeout
|
||||
? { request_timeout: opts.request_timeout }
|
||||
: {}),
|
||||
});
|
||||
|
||||
this.clientMap.set(key, client);
|
||||
@@ -122,8 +139,14 @@ export class ClickHouseClientManager {
|
||||
}
|
||||
}
|
||||
|
||||
export const clickhouseClient = (opts?: NodeClickHouseClientConfigOptions) => {
|
||||
return ClickHouseClientManager.getInstance().getClient(opts ?? {});
|
||||
export const clickhouseClient = (
|
||||
opts?: NodeClickHouseClientConfigOptions,
|
||||
preferredClickhouseService: PreferredClickhouseService = "ReadWrite",
|
||||
) => {
|
||||
return ClickHouseClientManager.getInstance().getClient(
|
||||
opts ?? {},
|
||||
preferredClickhouseService,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { instrumentAsync, recordDistribution } from "../instrumentation";
|
||||
import * as opentelemetry from "@opentelemetry/api";
|
||||
import { env } from "../../env";
|
||||
import { logger } from "../logger";
|
||||
import { instrumentAsync } from "../instrumentation";
|
||||
|
||||
const executionWrapper = async <T, Y>(
|
||||
input: T,
|
||||
@@ -20,16 +18,15 @@ const executionWrapper = async <T, Y>(
|
||||
};
|
||||
|
||||
/**
|
||||
* Measures the execution time of two functions and returns the result based on the experiment configuration.
|
||||
* This is used to compare the execution of AggregatingMergeTrees with the existing ReplacingMergeTree execution.
|
||||
* Measures the execution time of a query functions and returns the result based on the experiment configuration.
|
||||
* Presently this is but a simple wrapper around single execution, but it is designed to be
|
||||
* extended for A/B testing or canary releases.
|
||||
*/
|
||||
export const measureAndReturn = async <T, Y>(args: {
|
||||
operationName: string;
|
||||
projectId: string;
|
||||
input: T;
|
||||
existingExecution: (input: T) => Promise<Y>; // eslint-disable-line no-unused-vars
|
||||
newExecution: (input: T) => Promise<Y>; // eslint-disable-line no-unused-vars
|
||||
minStartTime?: Date;
|
||||
fn: (input: T) => Promise<Y>; // eslint-disable-line no-unused-vars
|
||||
}): Promise<Y> => {
|
||||
return instrumentAsync(
|
||||
{
|
||||
@@ -37,112 +34,17 @@ export const measureAndReturn = async <T, Y>(args: {
|
||||
spanKind: opentelemetry.SpanKind.CLIENT,
|
||||
},
|
||||
async (currentSpan) => {
|
||||
const { input, existingExecution, newExecution, minStartTime } = args;
|
||||
const { input, fn } = args;
|
||||
|
||||
if (
|
||||
env.LANGFUSE_EXPERIMENT_COMPARE_READ_FROM_AGGREGATING_MERGE_TREES !==
|
||||
"true"
|
||||
) {
|
||||
currentSpan.setAttribute(`langfuse.experiment.amts.run`, "disabled");
|
||||
// When we want do to multiple executions for A/B testing or canary releases,
|
||||
// or some other form of a more complex wrapper we used to try-catch
|
||||
// using the wrapper function and fallback to a simple f(input) when it failed.
|
||||
const [[existingResult, _existingDuration]] = // eslint-disable-line no-unused-vars
|
||||
await Promise.all([
|
||||
executionWrapper(input, fn, currentSpan, "existing"),
|
||||
]);
|
||||
|
||||
// Check for short-term new result experiment
|
||||
if (
|
||||
env.LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT_SHORT_TERM === "true" &&
|
||||
minStartTime
|
||||
) {
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
if (minStartTime >= thirtyDaysAgo) {
|
||||
currentSpan.setAttribute(
|
||||
`langfuse.experiment.amts.short-term`,
|
||||
"true",
|
||||
);
|
||||
return newExecution(input);
|
||||
}
|
||||
}
|
||||
|
||||
return env.LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT === "true"
|
||||
? newExecution(input)
|
||||
: existingExecution(input);
|
||||
}
|
||||
|
||||
// If not whitelisted, apply sampling logic
|
||||
if (
|
||||
!env.LANGFUSE_EXPERIMENT_WHITELISTED_PROJECT_IDS.includes(
|
||||
args.projectId,
|
||||
) &&
|
||||
Math.random() > env.LANGFUSE_EXPERIMENT_SAMPLING_RATE
|
||||
) {
|
||||
currentSpan.setAttribute(`langfuse.experiment.amts.run`, "sampled-out");
|
||||
return existingExecution(input);
|
||||
}
|
||||
|
||||
currentSpan.setAttribute(`langfuse.experiment.amts.run`, "true");
|
||||
|
||||
try {
|
||||
const [[existingResult, existingDuration], [newResult, newDuration]] =
|
||||
await Promise.all([
|
||||
executionWrapper(input, existingExecution, currentSpan, "existing"),
|
||||
executionWrapper(input, newExecution, currentSpan, "new"),
|
||||
]);
|
||||
// Positive duration difference means new is faster
|
||||
const durationDifference = existingDuration - newDuration;
|
||||
currentSpan?.setAttribute(
|
||||
"langfuse.experiment.amts.execution-time-difference",
|
||||
durationDifference,
|
||||
);
|
||||
|
||||
recordDistribution(
|
||||
"langfuse.experiment.amts.duration_difference_distribution",
|
||||
durationDifference,
|
||||
{
|
||||
operation: args.operationName,
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
env.LANGFUSE_EXPERIMENT_ADD_QUERY_RESULT_TO_SPAN_PROJECT_IDS.some(
|
||||
(p) => p === args.projectId,
|
||||
)
|
||||
) {
|
||||
currentSpan?.setAttribute(
|
||||
"langfuse.experiment.amts.existing-result",
|
||||
JSON.stringify(existingResult),
|
||||
);
|
||||
currentSpan?.setAttribute(
|
||||
"langfuse.experiment.amts.new-result",
|
||||
JSON.stringify(newResult),
|
||||
);
|
||||
}
|
||||
|
||||
// Check for short-term new result experiment
|
||||
if (
|
||||
env.LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT_SHORT_TERM === "true" &&
|
||||
minStartTime
|
||||
) {
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
if (minStartTime >= thirtyDaysAgo) {
|
||||
currentSpan.setAttribute(
|
||||
`langfuse.experiment.amts.short-term`,
|
||||
"true",
|
||||
);
|
||||
return newResult;
|
||||
}
|
||||
}
|
||||
|
||||
return env.LANGFUSE_EXPERIMENT_RETURN_NEW_RESULT === "true"
|
||||
? newResult
|
||||
: existingResult;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"Failed to run experiment wrapper. Retrying existing query",
|
||||
e,
|
||||
);
|
||||
return existingExecution(input);
|
||||
}
|
||||
return existingResult;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { env } from "../../env";
|
||||
import { logger } from "../../server/logger";
|
||||
import {
|
||||
DatasetRunItemsExecutionStrategy,
|
||||
DatasetRunItemsOperationType,
|
||||
} from "./types";
|
||||
/**
|
||||
* Returns the execution strategy for dataset run items based on environment variables.
|
||||
*
|
||||
* Two-phase migration approach:
|
||||
* 1. Dual-write phase: DATASET_RUN_ITEMS_WRITE_TO_CLICKHOUSE=true (write to both databases)
|
||||
* 2. Read migration phase: DATASET_RUN_ITEMS_READ_FROM_CLICKHOUSE=true (read from ClickHouse)
|
||||
*/
|
||||
function getDatasetRunItemsExecutionStrategy(): DatasetRunItemsExecutionStrategy {
|
||||
return {
|
||||
shouldWriteToClickHouse:
|
||||
env.LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_WRITE_CH === "true",
|
||||
shouldReadFromClickHouse:
|
||||
env.LANGFUSE_EXPERIMENT_DATASET_RUN_ITEMS_READ_CH === "true",
|
||||
};
|
||||
}
|
||||
|
||||
// Re-export the enum for backward compatibility
|
||||
|
||||
/**
|
||||
* Executes the appropriate database operation based on the execution strategy.
|
||||
*
|
||||
* @param postgresExecution - Function to execute PostgreSQL operation
|
||||
* @param clickhouseExecution - Function to execute ClickHouse operation
|
||||
* @param operationType - Type of operation ("read" or "write")
|
||||
* @returns Result from the selected execution strategy
|
||||
*/
|
||||
export async function executeWithDatasetRunItemsStrategy<TInput, TOutput>({
|
||||
input,
|
||||
operationType,
|
||||
postgresExecution,
|
||||
clickhouseExecution,
|
||||
}: {
|
||||
input: TInput;
|
||||
operationType: DatasetRunItemsOperationType;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
postgresExecution: (input: TInput) => Promise<TOutput>;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
clickhouseExecution: (input: TInput) => Promise<TOutput>;
|
||||
}): Promise<TOutput> {
|
||||
const strategy = getDatasetRunItemsExecutionStrategy();
|
||||
|
||||
if (operationType === DatasetRunItemsOperationType.WRITE) {
|
||||
// For write operations, implement dual-write strategy
|
||||
if (strategy.shouldWriteToClickHouse) {
|
||||
// Dual-write phase: write to both databases
|
||||
const postgresResult = await postgresExecution(input);
|
||||
|
||||
try {
|
||||
const clickhouseResult = await clickhouseExecution(input);
|
||||
logger.debug("Successfully wrote to both PostgreSQL and ClickHouse", {
|
||||
operation: `dataset_run_items_${operationType}`,
|
||||
});
|
||||
return clickhouseResult;
|
||||
} catch (error) {
|
||||
logger.error("ClickHouse write failed during dual-write phase", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
operation: `dataset_run_items_${operationType}`,
|
||||
});
|
||||
// Continue with PostgreSQL result since it succeeded
|
||||
}
|
||||
|
||||
return postgresResult;
|
||||
} else {
|
||||
// Write only to PostgreSQL
|
||||
return await postgresExecution(input);
|
||||
}
|
||||
} else {
|
||||
if (strategy.shouldReadFromClickHouse) {
|
||||
return await clickhouseExecution(input);
|
||||
} else {
|
||||
// Read from PostgreSQL
|
||||
return await postgresExecution(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Types and enums for dataset run items execution.
|
||||
* This file is frontend-safe and doesn't import server-side dependencies.
|
||||
*/
|
||||
|
||||
export enum DatasetRunItemsOperationType {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
READ = "read",
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
WRITE = "write",
|
||||
}
|
||||
|
||||
export type DatasetRunItemsExecutionStrategy = {
|
||||
shouldWriteToClickHouse: boolean;
|
||||
shouldReadFromClickHouse: boolean;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user