Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60bf74698a | ||
|
|
5ac7dc969e | ||
|
|
3b152eda21 | ||
|
|
bfde018060 | ||
|
|
d83bc01b22 | ||
|
|
feb811ccbb | ||
|
|
5a2f478613 | ||
|
|
29c726499e | ||
|
|
c15435126d | ||
|
|
343fdf6785 | ||
|
|
ada8f639f1 | ||
|
|
913c5e3b3b | ||
|
|
035b8a76df | ||
|
|
14717f56b2 | ||
|
|
bef70d0c2e | ||
|
|
5aaa509954 | ||
|
|
3026ee4b27 | ||
|
|
7e2dd0a42b | ||
|
|
7c4092a8c5 | ||
|
|
ae3464856c | ||
|
|
4501244544 | ||
|
|
8941ee0939 | ||
|
|
f0b8fc6aa6 | ||
|
|
9468c8ca93 | ||
|
|
cfd127c4db | ||
|
|
98a0cc29c4 | ||
|
|
27b3771861 | ||
|
|
bb2108e858 | ||
|
|
268160b214 | ||
|
|
9620fa8f63 | ||
|
|
1cab7db5e8 | ||
|
|
779ea774d0 | ||
|
|
e746188122 | ||
|
|
d5ccc0fc1b | ||
|
|
14a1764d81 | ||
|
|
8adef3a4b3 | ||
|
|
fd231782d9 | ||
|
|
2342ae1bd1 | ||
|
|
f90e3da4a9 | ||
|
|
23fa8dd19c | ||
|
|
1007507716 | ||
|
|
acf68a13cc | ||
|
|
17e8249913 | ||
|
|
0947acce66 | ||
|
|
8dc5087263 | ||
|
|
685abd6575 | ||
|
|
a3fbdee0bd | ||
|
|
6a24a50892 | ||
|
|
9be6ffb8ff | ||
|
|
938219203e | ||
|
|
036736d805 | ||
|
|
e6aeeff357 | ||
|
|
8799a550b4 | ||
|
|
4c4ab027d9 | ||
|
|
af4106d463 | ||
|
|
08ea1807b3 | ||
|
|
a2698664d0 | ||
|
|
096abd4b12 | ||
|
|
04f9a33f64 | ||
|
|
c9116d5a29 | ||
|
|
8cfafe1e61 | ||
|
|
6d08720436 | ||
|
|
d008a40788 |
@@ -20,6 +20,9 @@ NEXTAUTH_URL="http://localhost:3000"
|
||||
NEXTAUTH_SECRET="secret"
|
||||
SALT="salt"
|
||||
|
||||
# Use CSP headers to enforce HTTPS, optional
|
||||
LANGFUSE_CSP_ENFORCE_HTTPS="true"
|
||||
|
||||
# Docker only, optional
|
||||
# PORT=3000
|
||||
# HOSTNAME=localhost
|
||||
@@ -46,6 +49,14 @@ SALT="salt"
|
||||
# AUTH_AZURE_AD_CLIENT_SECRET=
|
||||
# AUTH_AZURE_AD_TENANT_ID=
|
||||
# AUTH_AZURE_ALLOW_ACCOUNT_LINKING=false
|
||||
# AUTH_OKTA_CLIENT_ID=
|
||||
# AUTH_OKTA_CLIENT_SECRET=
|
||||
# AUTH_OKTA_ISSUER=
|
||||
# AUTH_OKTA_ALLOW_ACCOUNT_LINKING=false
|
||||
# AUTH_AUTH0_CLIENT_ID=
|
||||
# AUTH_AUTH0_CLIENT_SECRET=
|
||||
# AUTH_AUTH0_ISSUER=
|
||||
# AUTH_AUTH0_ALLOW_ACCOUNT_LINKING=false
|
||||
|
||||
# Transactional email, optional
|
||||
# Defines the email address to use as the from address.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* @langfuse/founders
|
||||
+2
-29
@@ -6,37 +6,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/worker" # Location of package manifests
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
rebase-strategy: "disabled" # use dependabot-rebase-stale
|
||||
commit-message:
|
||||
prefix: chore
|
||||
prefix-development: chore
|
||||
include: scope
|
||||
ignore:
|
||||
- dependency-name: "@types/node"
|
||||
- dependency-name: "@trpc/*"
|
||||
groups:
|
||||
sentry:
|
||||
patterns:
|
||||
- "@sentry/*"
|
||||
prisma:
|
||||
patterns:
|
||||
- "prisma"
|
||||
- "@prisma/*"
|
||||
next:
|
||||
patterns:
|
||||
- "eslint-config-next"
|
||||
- "next"
|
||||
patches:
|
||||
update-types:
|
||||
- "patch"
|
||||
- package-ecosystem: npm
|
||||
directory: "/web" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
rebase-strategy: "disabled" # use dependabot-rebase-stale
|
||||
versioning-strategy: "increase"
|
||||
commit-message:
|
||||
prefix: chore
|
||||
prefix-development: chore
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Start containers
|
||||
run: docker-compose -f "docker-compose.yml" up -d --build
|
||||
run: docker compose -f "docker-compose.yml" up -d --build
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
|
||||
- name: Seed DB
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
|
||||
- name: Run + migrate
|
||||
run: |
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
sleep 5 # Wait for PostgreSQL to accept connections
|
||||
|
||||
- name: Seed DB
|
||||
@@ -140,9 +140,7 @@ jobs:
|
||||
run: pnpm run build
|
||||
|
||||
- name: Install playwright
|
||||
run: |
|
||||
npm add --global playwright
|
||||
pnpm exec playwright install
|
||||
run: pnpm --filter=web exec playwright install --with-deps
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pnpm --filter=web run test:e2e
|
||||
|
||||
+3
-1
@@ -55,8 +55,10 @@ A good first step is to search for open [issues](https://github.com/langfuse/lan
|
||||
flowchart TB
|
||||
subgraph s4["Clients"]
|
||||
subgraph s2["langfuse/langfuse-python"]
|
||||
Python["Python SDK"]
|
||||
Python["Python low-level SDK"]
|
||||
Decorator["observe() decorator"] -->|extends| Python
|
||||
OAI["OpenAI drop-in replacement"] -->|extends| Python
|
||||
Llamaindex["LlamaIndex Integration"] -->|extends| Python
|
||||
LCPYTHON["Langchain Python Integration"] -->|extends| Python
|
||||
Langflow -->|uses| LCPYTHON
|
||||
LiteLLM -->|uses| Python
|
||||
|
||||
@@ -122,21 +122,36 @@ types:
|
||||
observationId: optional<string>
|
||||
timestamp: datetime
|
||||
comment: optional<string>
|
||||
Dataset:
|
||||
DatasetCore:
|
||||
properties:
|
||||
id: string
|
||||
name: string
|
||||
description: optional<string>
|
||||
projectId: string
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
DatasetWithReferences: # GET datasets
|
||||
extends: DatasetCore
|
||||
properties:
|
||||
items:
|
||||
type: list<string>
|
||||
docs: list of dataset item ids
|
||||
runs:
|
||||
type: list<string>
|
||||
docs: list of dataset run names
|
||||
Dataset: # GET datasets/[name]
|
||||
docs: Dataset including all items
|
||||
extends: DatasetCore
|
||||
properties:
|
||||
items: list<DatasetItem>
|
||||
runs: list<string>
|
||||
DatasetItem:
|
||||
properties:
|
||||
id: string
|
||||
status: DatasetStatus
|
||||
input: unknown
|
||||
input: optional<unknown>
|
||||
expectedOutput: optional<unknown>
|
||||
sourceTraceId: optional<string>
|
||||
sourceObservationId: optional<string>
|
||||
datasetId: string
|
||||
createdAt: datetime
|
||||
@@ -146,15 +161,18 @@ types:
|
||||
id: string
|
||||
datasetRunId: string
|
||||
datasetItemId: string
|
||||
observationId: string
|
||||
traceId: string
|
||||
observationId: optional<string>
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
DatasetRun:
|
||||
properties:
|
||||
id: string
|
||||
name: string
|
||||
description: optional<string>
|
||||
metadata: optional<unknown>
|
||||
datasetId: string
|
||||
datasetName: string
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
datasetRunItems: list<DatasetRunItem>
|
||||
|
||||
@@ -24,7 +24,7 @@ types:
|
||||
CreateDatasetItemRequest:
|
||||
properties:
|
||||
datasetName: string
|
||||
input: unknown
|
||||
input: optional<unknown>
|
||||
expectedOutput: optional<unknown>
|
||||
id:
|
||||
type: optional<string>
|
||||
|
||||
@@ -15,8 +15,14 @@ types:
|
||||
CreateDatasetRunItemRequest:
|
||||
properties:
|
||||
runName: string
|
||||
runDescription:
|
||||
type: optional<string>
|
||||
docs: Description of the run. If run exists, description will be updated.
|
||||
metadata:
|
||||
type: optional<unknown>
|
||||
docs: Metadata of the dataset run, updates run if run already exists
|
||||
datasetItemId: string
|
||||
observationId: string
|
||||
observationId: optional<string>
|
||||
traceId:
|
||||
type: optional<string>
|
||||
docs: traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId.
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
|
||||
imports:
|
||||
commons: ./commons.yml
|
||||
pagination: ./utils/pagination.yml
|
||||
service:
|
||||
auth: true
|
||||
base-path: /api/public
|
||||
endpoints:
|
||||
list:
|
||||
method: GET
|
||||
docs: Get all datasets
|
||||
path: /datasets
|
||||
request:
|
||||
name: GetDatasetsRequest
|
||||
query-parameters:
|
||||
page: optional<integer>
|
||||
limit: optional<integer>
|
||||
response: PaginatedDatasets
|
||||
get:
|
||||
method: GET
|
||||
docs: Get a dataset and its items
|
||||
@@ -28,6 +39,11 @@ service:
|
||||
response: commons.DatasetRun
|
||||
|
||||
types:
|
||||
PaginatedDatasets:
|
||||
properties:
|
||||
data: list<commons.DatasetWithReferences>
|
||||
meta: pagination.MetaResponse
|
||||
CreateDatasetRequest:
|
||||
properties:
|
||||
name: string
|
||||
description: optional<string>
|
||||
|
||||
@@ -13,8 +13,12 @@ service:
|
||||
request:
|
||||
name: GetDailyMetricsRequest
|
||||
query-parameters:
|
||||
page: optional<integer>
|
||||
limit: optional<integer>
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
traceName:
|
||||
type: optional<string>
|
||||
docs: Optional filter by the name of the trace
|
||||
|
||||
@@ -22,13 +22,20 @@ service:
|
||||
request:
|
||||
name: GetObservationsRequest
|
||||
query-parameters:
|
||||
page: optional<integer>
|
||||
limit: optional<integer>
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
name: optional<string>
|
||||
userId: optional<string>
|
||||
type: optional<string>
|
||||
traceId: optional<string>
|
||||
parentObservationId: optional<string>
|
||||
fromStartTime:
|
||||
type: optional<datetime>
|
||||
docs: Retrieve only observations with a start_time greater than this timestamp.
|
||||
response: ObservationsViews
|
||||
|
||||
types:
|
||||
|
||||
@@ -19,10 +19,17 @@ service:
|
||||
request:
|
||||
name: GetScoresRequest
|
||||
query-parameters:
|
||||
page: optional<integer>
|
||||
limit: optional<integer>
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
userId: optional<string>
|
||||
name: optional<string>
|
||||
fromTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Retrieve only scores newer than this timestamp.
|
||||
response: Scores
|
||||
get-by-id:
|
||||
docs: Get a score
|
||||
|
||||
@@ -22,10 +22,17 @@ service:
|
||||
request:
|
||||
name: GetTracesRequest
|
||||
query-parameters:
|
||||
page: optional<integer>
|
||||
limit: optional<integer>
|
||||
page:
|
||||
type: optional<integer>
|
||||
docs: page number, starts at 1
|
||||
limit:
|
||||
type: optional<integer>
|
||||
docs: limit of items per page
|
||||
userId: optional<string>
|
||||
name: optional<string>
|
||||
fromTimestamp:
|
||||
type: optional<datetime>
|
||||
docs: Retrieve only traces newer than this timestamp.
|
||||
orderBy:
|
||||
type: optional<string>
|
||||
docs: "Format of the string [field].[asc/desc]. Fields: id, timestamp, name, userId, release, version, public, bookmarked, sessionId. Example: timestamp.asc"
|
||||
|
||||
@@ -11,14 +11,16 @@ groups:
|
||||
namespaceExport: Langfuse
|
||||
allowCustomFetcher: true
|
||||
- name: fernapi/fern-python-sdk
|
||||
version: 0.7.3
|
||||
version: 1.1.0-rc2
|
||||
output:
|
||||
location: local-file-system
|
||||
path: ../../../generated/python
|
||||
config:
|
||||
client_class_name: FernLangfuse
|
||||
improved_imports: false
|
||||
pydantic_config:
|
||||
require_optional_fields: false
|
||||
use_str_enums: false
|
||||
- name: fernapi/fern-typescript-node-sdk
|
||||
version: 0.7.1
|
||||
output:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"organization": "finto",
|
||||
"version": "0.16.36"
|
||||
"version": "0.19.30"
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: langfuse
|
||||
version: ''
|
||||
paths:
|
||||
/api/public/scores:
|
||||
post:
|
||||
description: Add a score to the database, upserts on id
|
||||
operationId: score_create
|
||||
tags:
|
||||
- Score
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Score'
|
||||
'400':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
'401':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
'403':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
'405':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateScoreRequest'
|
||||
components:
|
||||
schemas:
|
||||
CreateScoreRequest:
|
||||
title: CreateScoreRequest
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
format: double
|
||||
observationId:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
required:
|
||||
- traceId
|
||||
- name
|
||||
- value
|
||||
Score:
|
||||
title: Score
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
format: double
|
||||
observationId:
|
||||
type: string
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
comment:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- traceId
|
||||
- name
|
||||
- value
|
||||
- timestamp
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
File diff suppressed because it is too large
Load Diff
@@ -137,7 +137,7 @@
|
||||
"auth": null,
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"runName\": \"example\",\n \"metadata\": \"UNKNOWN\",\n \"datasetItemId\": \"example\",\n \"observationId\": \"example\"\n}",
|
||||
"raw": "{\n \"runName\": \"example\",\n \"runDescription\": \"example\",\n \"metadata\": \"UNKNOWN\",\n \"datasetItemId\": \"example\",\n \"observationId\": \"example\",\n \"traceId\": \"example\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -154,6 +154,42 @@
|
||||
"description": null,
|
||||
"name": "Datasets",
|
||||
"item": [
|
||||
{
|
||||
"_type": "endpoint",
|
||||
"name": "List",
|
||||
"request": {
|
||||
"description": "Get all datasets",
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/public/datasets?page=&limit=",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"public",
|
||||
"datasets"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "page",
|
||||
"value": "",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "",
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"variable": []
|
||||
},
|
||||
"header": [],
|
||||
"method": "GET",
|
||||
"auth": null,
|
||||
"body": null
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"_type": "endpoint",
|
||||
"name": "Get",
|
||||
@@ -209,7 +245,7 @@
|
||||
"auth": null,
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"example\"\n}",
|
||||
"raw": "{\n \"name\": \"example\",\n \"description\": \"example\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -357,12 +393,12 @@
|
||||
{
|
||||
"key": "page",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "page number, starts at 1"
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "limit of items per page"
|
||||
},
|
||||
{
|
||||
"key": "traceName",
|
||||
@@ -434,7 +470,7 @@
|
||||
"request": {
|
||||
"description": "Get a list of observations",
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/public/observations?page=&limit=&name=&userId=&type=&traceId=&parentObservationId=",
|
||||
"raw": "{{baseUrl}}/api/public/observations?page=&limit=&name=&userId=&type=&traceId=&parentObservationId=&fromStartTime=",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
@@ -447,12 +483,12 @@
|
||||
{
|
||||
"key": "page",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "page number, starts at 1"
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "limit of items per page"
|
||||
},
|
||||
{
|
||||
"key": "name",
|
||||
@@ -478,6 +514,11 @@
|
||||
"key": "parentObservationId",
|
||||
"value": "",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"key": "fromStartTime",
|
||||
"value": "",
|
||||
"description": "Retrieve only observations with a start_time greater than this timestamp."
|
||||
}
|
||||
],
|
||||
"variable": []
|
||||
@@ -643,7 +684,7 @@
|
||||
"request": {
|
||||
"description": "Get a list of scores",
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/public/scores?page=&limit=&userId=&name=",
|
||||
"raw": "{{baseUrl}}/api/public/scores?page=&limit=&userId=&name=&fromTimestamp=",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
@@ -656,12 +697,12 @@
|
||||
{
|
||||
"key": "page",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "page number, starts at 1"
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "limit of items per page"
|
||||
},
|
||||
{
|
||||
"key": "userId",
|
||||
@@ -672,6 +713,11 @@
|
||||
"key": "name",
|
||||
"value": "",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"key": "fromTimestamp",
|
||||
"value": "",
|
||||
"description": "Retrieve only scores newer than this timestamp."
|
||||
}
|
||||
],
|
||||
"variable": []
|
||||
@@ -831,7 +877,7 @@
|
||||
"request": {
|
||||
"description": "Get list of traces",
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/public/traces?page=&limit=&userId=&name=&orderBy=&tags=",
|
||||
"raw": "{{baseUrl}}/api/public/traces?page=&limit=&userId=&name=&fromTimestamp=&orderBy=&tags=",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
@@ -844,12 +890,12 @@
|
||||
{
|
||||
"key": "page",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "page number, starts at 1"
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "",
|
||||
"description": null
|
||||
"description": "limit of items per page"
|
||||
},
|
||||
{
|
||||
"key": "userId",
|
||||
@@ -861,6 +907,11 @@
|
||||
"value": "",
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"key": "fromTimestamp",
|
||||
"value": "",
|
||||
"description": "Retrieve only traces newer than this timestamp."
|
||||
},
|
||||
{
|
||||
"key": "orderBy",
|
||||
"value": "",
|
||||
|
||||
+5
-5
@@ -8,14 +8,14 @@
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"infra:dev:up": "docker-compose -f ./docker-compose.dev.yml up -d",
|
||||
"infra:dev:down": "docker-compose -f ./docker-compose.dev.yml down",
|
||||
"infra:dev:up": "docker compose -f ./docker-compose.dev.yml up -d",
|
||||
"infra:dev:down": "docker compose -f ./docker-compose.dev.yml down",
|
||||
"db:generate": "turbo run db:generate",
|
||||
"db:migrate": "turbo run db:migrate",
|
||||
"db:seed": "turbo run db:seed",
|
||||
"db:seed:examples": "turbo run db:seed:examples",
|
||||
"nuke": "bash ./scripts/nuke.sh",
|
||||
"dx": "pnpm i && pnpm --filter=shared run db:reset && pnpm --filter=worker run db:seed:examples && pnpm run dev",
|
||||
"dx": "pnpm i && pnpm --filter=shared run db:reset && pnpm --filter=shared run db:seed:examples && pnpm run dev",
|
||||
"build": "turbo run build",
|
||||
"start": "turbo run start",
|
||||
"dev": "turbo run dev",
|
||||
@@ -24,8 +24,8 @@
|
||||
"models:migrate": "turbo run models:migrate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"dotenv-cli": "^7.4.1",
|
||||
"prettier": "^3.2.5",
|
||||
"turbo": "^1.13.0"
|
||||
"turbo": "^1.13.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
"next.js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@vercel/style-guide": "^5.2.0",
|
||||
"@vercel/style-guide": "^6.0.0",
|
||||
"eslint-config-turbo": "^1.12.4",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-only-warn": "^1.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
"seed": "ts-node -r tsconfig-paths/register -r dotenv/config --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^0.1.8",
|
||||
"@langchain/core": "^0.1.49",
|
||||
"@langchain/openai": "^0.0.23",
|
||||
"@prisma/client": "^5.11.0",
|
||||
"@langchain/anthropic": "^0.1.10",
|
||||
"@langchain/core": "^0.1.54",
|
||||
"@langchain/openai": "^0.0.26",
|
||||
"@prisma/client": "^5.12.1",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"kysely": "^0.27.3",
|
||||
"langchain": "^0.1.28",
|
||||
"langchain": "^0.1.31",
|
||||
"prisma-extension-kysely": "^2.1.0",
|
||||
"prisma-kysely": "^1.8.0",
|
||||
"zod": "^3.22.4"
|
||||
@@ -45,7 +45,7 @@
|
||||
"@repo/typescript-config": "*",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "^20.11.29",
|
||||
"@types/pg": "^8.11.2",
|
||||
"@types/pg": "^8.11.4",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"eslint": "^8.57.0",
|
||||
@@ -55,12 +55,12 @@
|
||||
"kysely-codegen": "^0.11.0",
|
||||
"nodemon": "^3.0.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prisma": "^5.11.0",
|
||||
"prisma": "^5.12.1",
|
||||
"prisma-erd-generator": "^1.11.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"tsup": "^8.0.2",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript": "^5.4.4",
|
||||
"vitest": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ export type CronJobs = {
|
||||
export type Dataset = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
project_id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
@@ -102,8 +103,9 @@ export type Dataset = {
|
||||
export type DatasetItem = {
|
||||
id: string;
|
||||
status: Generated<DatasetStatus>;
|
||||
input: unknown;
|
||||
input: unknown | null;
|
||||
expected_output: unknown | null;
|
||||
source_trace_id: string | null;
|
||||
source_observation_id: string | null;
|
||||
dataset_id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
@@ -113,13 +115,15 @@ export type DatasetRunItems = {
|
||||
id: string;
|
||||
dataset_run_id: string;
|
||||
dataset_item_id: string;
|
||||
observation_id: string;
|
||||
trace_id: string;
|
||||
observation_id: string | null;
|
||||
created_at: Generated<Timestamp>;
|
||||
updated_at: Generated<Timestamp>;
|
||||
};
|
||||
export type DatasetRuns = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
metadata: unknown | null;
|
||||
dataset_id: string;
|
||||
created_at: Generated<Timestamp>;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "dataset_run_items" ADD COLUMN "trace_id" TEXT,
|
||||
ALTER COLUMN "observation_id" DROP NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dataset_run_items" ADD CONSTRAINT "dataset_run_items_trace_id_fkey" FOREIGN KEY ("trace_id") REFERENCES "traces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- Backfill trace_id for existing run_items based on the linked observation
|
||||
UPDATE dataset_run_items
|
||||
SET trace_id = observations.trace_id
|
||||
FROM observations
|
||||
WHERE dataset_run_items.observation_id = observations.id;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `trace_id` on table `dataset_run_items` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "dataset_run_items" ALTER COLUMN "trace_id" SET NOT NULL;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX CONCURRENTLY "dataset_run_items_trace_id_idx" ON "dataset_run_items"("trace_id");
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "dataset_items" ALTER COLUMN "input" DROP NOT NULL;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "dataset_items" ADD COLUMN "source_trace_id" TEXT;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "dataset_items" ADD CONSTRAINT "dataset_items_source_trace_id_fkey" FOREIGN KEY ("source_trace_id") REFERENCES "traces"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "dataset_runs" ADD COLUMN "description" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "datasets" ADD COLUMN "description" TEXT;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
-- Backfill source_trace_id for existing dataset_items based on the linked source_observation_id
|
||||
UPDATE dataset_items
|
||||
SET source_trace_id = observations.trace_id
|
||||
FROM observations
|
||||
WHERE dataset_items.source_observation_id = observations.id
|
||||
AND dataset_items.source_observation_id IS NOT NULL
|
||||
AND dataset_items.source_trace_id IS NULL
|
||||
@@ -206,7 +206,9 @@ model Trace {
|
||||
sessionId String? @map("session_id")
|
||||
session TraceSession? @relation(fields: [sessionId, projectId], references: [id, projectId])
|
||||
|
||||
scores Score[]
|
||||
scores Score[]
|
||||
DatasetRunItems DatasetRunItems[]
|
||||
DatasetItem DatasetItem[]
|
||||
|
||||
@@index([projectId])
|
||||
@@index([sessionId])
|
||||
@@ -420,6 +422,7 @@ model CronJobs {
|
||||
model Dataset {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
description String?
|
||||
projectId String @map("project_id")
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
@@ -435,8 +438,10 @@ model Dataset {
|
||||
model DatasetItem {
|
||||
id String @id @default(cuid())
|
||||
status DatasetStatus @default(ACTIVE)
|
||||
input Json
|
||||
input Json?
|
||||
expectedOutput Json? @map("expected_output")
|
||||
sourceTraceId String? @map("source_trace_id")
|
||||
sourceTrace Trace? @relation(fields: [sourceTraceId], references: [id], onDelete: SetNull)
|
||||
sourceObservationId String? @map("source_observation_id")
|
||||
sourceObservation Observation? @relation(fields: [sourceObservationId], references: [id], onDelete: SetNull)
|
||||
datasetId String @map("dataset_id")
|
||||
@@ -458,6 +463,7 @@ enum DatasetStatus {
|
||||
model DatasetRuns {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
description String?
|
||||
metadata Json?
|
||||
datasetId String @map("dataset_id")
|
||||
dataset Dataset @relation(fields: [datasetId], references: [id], onDelete: Cascade)
|
||||
@@ -471,19 +477,22 @@ model DatasetRuns {
|
||||
}
|
||||
|
||||
model DatasetRunItems {
|
||||
id String @id @default(cuid())
|
||||
datasetRunId String @map("dataset_run_id")
|
||||
datasetRun DatasetRuns @relation(fields: [datasetRunId], references: [id], onDelete: Cascade)
|
||||
datasetItemId String @map("dataset_item_id")
|
||||
datasetItem DatasetItem @relation(fields: [datasetItemId], references: [id], onDelete: Cascade)
|
||||
observationId String @map("observation_id")
|
||||
observation Observation @relation(fields: [observationId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
id String @id @default(cuid())
|
||||
datasetRunId String @map("dataset_run_id")
|
||||
datasetRun DatasetRuns @relation(fields: [datasetRunId], references: [id], onDelete: Cascade)
|
||||
datasetItemId String @map("dataset_item_id")
|
||||
datasetItem DatasetItem @relation(fields: [datasetItemId], references: [id], onDelete: Cascade)
|
||||
traceId String @map("trace_id")
|
||||
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
|
||||
observationId String? @map("observation_id")
|
||||
observation Observation? @relation(fields: [observationId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@index([datasetRunId], type: Hash)
|
||||
@@index([datasetItemId], type: Hash)
|
||||
@@index([observationId], type: Hash)
|
||||
@@index([traceId])
|
||||
@@map("dataset_run_items")
|
||||
}
|
||||
|
||||
|
||||
@@ -149,15 +149,14 @@ async function main() {
|
||||
|
||||
const traceVolume = environment === "load" ? LOAD_TRACE_VOLUME : 100;
|
||||
|
||||
const { generationIds, traces, observations, scores, sessions, events } =
|
||||
createObjects(
|
||||
traceVolume,
|
||||
envTags,
|
||||
colorTags,
|
||||
project1,
|
||||
project2,
|
||||
promptIds
|
||||
);
|
||||
const { traces, observations, scores, sessions, events } = createObjects(
|
||||
traceVolume,
|
||||
envTags,
|
||||
colorTags,
|
||||
project1,
|
||||
project2,
|
||||
promptIds
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Seeding ${traces.length} traces, ${observations.length} observations, and ${scores.length} scores`
|
||||
@@ -169,28 +168,37 @@ async function main() {
|
||||
const dataset = await prisma.dataset.create({
|
||||
data: {
|
||||
name: `demo-dataset-${datasetNumber}`,
|
||||
description:
|
||||
datasetNumber === 0 ? "Dataset test description" : undefined,
|
||||
projectId: project2.id,
|
||||
},
|
||||
});
|
||||
|
||||
const datasetItemIds = [];
|
||||
for (let i = 0; i < 18; i++) {
|
||||
const sourceObservationId =
|
||||
Math.random() > 0.5
|
||||
? generationIds[Math.floor(Math.random() * generationIds.length)]
|
||||
const sourceObservation =
|
||||
Math.random() > 0.3
|
||||
? observations[Math.floor(Math.random() * observations.length)]
|
||||
: undefined;
|
||||
const datasetItem = await prisma.datasetItem.create({
|
||||
data: {
|
||||
datasetId: dataset.id,
|
||||
sourceObservationId: sourceObservationId,
|
||||
input: [
|
||||
{
|
||||
role: "user",
|
||||
content: "How can i create a React component?",
|
||||
},
|
||||
],
|
||||
sourceTraceId: sourceObservation?.traceId,
|
||||
sourceObservationId:
|
||||
Math.random() > 0.5 ? sourceObservation?.id : undefined,
|
||||
input:
|
||||
Math.random() > 0.3
|
||||
? [
|
||||
{
|
||||
role: "user",
|
||||
content: "How can i create a React component?",
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
expectedOutput:
|
||||
"Creating a React component can be done in two ways: as a functional component or as a class component. Let's start with a basic example of both.",
|
||||
Math.random() > 0.3
|
||||
? "Creating a React component can be done in two ways: as a functional component or as a class component. Let's start with a basic example of both."
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
datasetItemIds.push(datasetItem.id);
|
||||
@@ -200,6 +208,7 @@ async function main() {
|
||||
const datasetRun = await prisma.datasetRuns.create({
|
||||
data: {
|
||||
name: `demo-dataset-run-${datasetRunNumber}`,
|
||||
description: Math.random() > 0.5 ? "Dataset run description" : "",
|
||||
datasetId: dataset.id,
|
||||
metadata: [
|
||||
undefined,
|
||||
@@ -212,13 +221,19 @@ async function main() {
|
||||
});
|
||||
|
||||
for (const datasetItemId of datasetItemIds) {
|
||||
const runObservationId =
|
||||
generationIds[Math.floor(Math.random() * generationIds.length)];
|
||||
const relevantObservations = observations.filter(
|
||||
(o) => o.projectId === project2.id
|
||||
);
|
||||
const observation =
|
||||
relevantObservations[
|
||||
Math.floor(Math.random() * relevantObservations.length)
|
||||
];
|
||||
|
||||
await prisma.datasetRunItems.create({
|
||||
data: {
|
||||
datasetItemId,
|
||||
observationId: runObservationId,
|
||||
traceId: observation.traceId as string,
|
||||
observationId: Math.random() > 0.5 ? observation.id : undefined,
|
||||
datasetRunId: datasetRun.id,
|
||||
},
|
||||
});
|
||||
@@ -354,7 +369,6 @@ function createObjects(
|
||||
const observations: Prisma.ObservationCreateManyInput[] = [];
|
||||
const scores: Prisma.ScoreCreateManyInput[] = [];
|
||||
const sessions: Prisma.TraceSessionCreateManyInput[] = [];
|
||||
const generationIds: string[] = [];
|
||||
const events: Prisma.ObservationCreateManyInput[] = [];
|
||||
|
||||
for (let i = 0; i < traceVolume; i++) {
|
||||
@@ -618,8 +632,6 @@ function createObjects(
|
||||
source: ScoreSource.API,
|
||||
});
|
||||
|
||||
generationIds.push(generation.id);
|
||||
|
||||
for (let l = 0; l < Math.floor(Math.random() * 2); l++) {
|
||||
// random start time within span
|
||||
const eventTs = new Date(
|
||||
@@ -651,7 +663,6 @@ function createObjects(
|
||||
).map((session) => JSON.parse(session) as Prisma.TraceSessionCreateManyInput);
|
||||
|
||||
return {
|
||||
generationIds,
|
||||
traces,
|
||||
observations,
|
||||
scores,
|
||||
|
||||
Generated
+1126
-1188
File diff suppressed because it is too large
Load Diff
@@ -152,25 +152,32 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateDatasetRunItemRequest'
|
||||
/api/public/datasets/{datasetName}:
|
||||
/api/public/datasets:
|
||||
get:
|
||||
description: Get a dataset and its items
|
||||
operationId: datasets_get
|
||||
description: Get all datasets
|
||||
operationId: datasets_list
|
||||
tags:
|
||||
- Datasets
|
||||
parameters:
|
||||
- name: datasetName
|
||||
in: path
|
||||
required: true
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
type: integer
|
||||
nullable: true
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
nullable: true
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Dataset'
|
||||
$ref: '#/components/schemas/PaginatedDatasets'
|
||||
'400':
|
||||
description: ''
|
||||
content:
|
||||
@@ -197,7 +204,6 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
security: *ref_0
|
||||
/api/public/datasets:
|
||||
post:
|
||||
description: Create a dataset
|
||||
operationId: datasets_create
|
||||
@@ -243,6 +249,51 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateDatasetRequest'
|
||||
/api/public/datasets/{datasetName}:
|
||||
get:
|
||||
description: Get a dataset and its items
|
||||
operationId: datasets_get
|
||||
tags:
|
||||
- Datasets
|
||||
parameters:
|
||||
- name: datasetName
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Dataset'
|
||||
'400':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
'401':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
'403':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
'404':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
'405':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
security: *ref_0
|
||||
/api/public/datasets/{datasetName}/runs/{runName}:
|
||||
get:
|
||||
description: Get a dataset run and its items
|
||||
@@ -396,12 +447,14 @@ paths:
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: page number, starts at 1
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
nullable: true
|
||||
- name: limit
|
||||
in: query
|
||||
description: limit of items per page
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
@@ -519,12 +572,14 @@ paths:
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: page number, starts at 1
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
nullable: true
|
||||
- name: limit
|
||||
in: query
|
||||
description: limit of items per page
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
@@ -559,6 +614,16 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
- name: fromStartTime
|
||||
in: query
|
||||
description: >-
|
||||
Retrieve only observations with a start_time greater than this
|
||||
timestamp.
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
@@ -782,12 +847,14 @@ paths:
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: page number, starts at 1
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
nullable: true
|
||||
- name: limit
|
||||
in: query
|
||||
description: limit of items per page
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
@@ -804,6 +871,14 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
- name: fromTimestamp
|
||||
in: query
|
||||
description: Retrieve only scores newer than this timestamp.
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
@@ -1025,12 +1100,14 @@ paths:
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: page number, starts at 1
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
nullable: true
|
||||
- name: limit
|
||||
in: query
|
||||
description: limit of items per page
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
@@ -1047,6 +1124,14 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
- name: fromTimestamp
|
||||
in: query
|
||||
description: Retrieve only traces newer than this timestamp.
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
- name: orderBy
|
||||
in: query
|
||||
description: >-
|
||||
@@ -1394,14 +1479,17 @@ components:
|
||||
- value
|
||||
- source
|
||||
- timestamp
|
||||
Dataset:
|
||||
title: Dataset
|
||||
DatasetCore:
|
||||
title: DatasetCore
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
nullable: true
|
||||
projectId:
|
||||
type: string
|
||||
createdAt:
|
||||
@@ -1410,6 +1498,36 @@ components:
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- projectId
|
||||
- createdAt
|
||||
- updatedAt
|
||||
DatasetWithReferences:
|
||||
title: DatasetWithReferences
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: list of dataset item ids
|
||||
runs:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: list of dataset run names
|
||||
required:
|
||||
- items
|
||||
- runs
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/DatasetCore'
|
||||
Dataset:
|
||||
title: Dataset
|
||||
type: object
|
||||
description: Dataset including all items
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
@@ -1419,13 +1537,10 @@ components:
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- projectId
|
||||
- createdAt
|
||||
- updatedAt
|
||||
- items
|
||||
- runs
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/DatasetCore'
|
||||
DatasetItem:
|
||||
title: DatasetItem
|
||||
type: object
|
||||
@@ -1434,9 +1549,13 @@ components:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/components/schemas/DatasetStatus'
|
||||
input: {}
|
||||
input:
|
||||
nullable: true
|
||||
expectedOutput:
|
||||
nullable: true
|
||||
sourceTraceId:
|
||||
type: string
|
||||
nullable: true
|
||||
sourceObservationId:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -1451,7 +1570,6 @@ components:
|
||||
required:
|
||||
- id
|
||||
- status
|
||||
- input
|
||||
- datasetId
|
||||
- createdAt
|
||||
- updatedAt
|
||||
@@ -1465,8 +1583,11 @@ components:
|
||||
type: string
|
||||
datasetItemId:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
observationId:
|
||||
type: string
|
||||
nullable: true
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -1477,7 +1598,7 @@ components:
|
||||
- id
|
||||
- datasetRunId
|
||||
- datasetItemId
|
||||
- observationId
|
||||
- traceId
|
||||
- createdAt
|
||||
- updatedAt
|
||||
DatasetRun:
|
||||
@@ -1488,10 +1609,15 @@ components:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
nullable: true
|
||||
metadata:
|
||||
nullable: true
|
||||
datasetId:
|
||||
type: string
|
||||
datasetName:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -1506,6 +1632,7 @@ components:
|
||||
- id
|
||||
- name
|
||||
- datasetId
|
||||
- datasetName
|
||||
- createdAt
|
||||
- updatedAt
|
||||
- datasetRunItems
|
||||
@@ -1558,7 +1685,8 @@ components:
|
||||
properties:
|
||||
datasetName:
|
||||
type: string
|
||||
input: {}
|
||||
input:
|
||||
nullable: true
|
||||
expectedOutput:
|
||||
nullable: true
|
||||
id:
|
||||
@@ -1567,13 +1695,16 @@ components:
|
||||
description: Dataset items are upserted on their id
|
||||
required:
|
||||
- datasetName
|
||||
- input
|
||||
CreateDatasetRunItemRequest:
|
||||
title: CreateDatasetRunItemRequest
|
||||
type: object
|
||||
properties:
|
||||
runName:
|
||||
type: string
|
||||
runDescription:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Description of the run. If run exists, description will be updated.
|
||||
metadata:
|
||||
nullable: true
|
||||
description: Metadata of the dataset run, updates run if run already exists
|
||||
@@ -1581,16 +1712,38 @@ components:
|
||||
type: string
|
||||
observationId:
|
||||
type: string
|
||||
nullable: true
|
||||
traceId:
|
||||
type: string
|
||||
nullable: true
|
||||
description: >-
|
||||
traceId should always be provided. For compatibility with older SDK
|
||||
versions it can also be inferred from the provided observationId.
|
||||
required:
|
||||
- runName
|
||||
- datasetItemId
|
||||
- observationId
|
||||
PaginatedDatasets:
|
||||
title: PaginatedDatasets
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DatasetWithReferences'
|
||||
meta:
|
||||
$ref: '#/components/schemas/utilsMetaResponse'
|
||||
required:
|
||||
- data
|
||||
- meta
|
||||
CreateDatasetRequest:
|
||||
title: CreateDatasetRequest
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
nullable: true
|
||||
required:
|
||||
- name
|
||||
HealthResponse:
|
||||
|
||||
+56
-16
@@ -6,6 +6,25 @@ await import("./src/env.mjs");
|
||||
import { withSentryConfig } from "@sentry/nextjs";
|
||||
import { env } from "./src/env.mjs";
|
||||
|
||||
/**
|
||||
* CSP headers
|
||||
* img-src https to allow loading images from SSO providers
|
||||
*/
|
||||
const cspHeader = `
|
||||
default-src 'self' https://ph.langfuse.com https://*.posthog.com wss://*.crisp.chat https://*.crisp.chat;
|
||||
script-src 'self' 'unsafe-eval' https://*.crisp.chat https://challenges.cloudflare.com https://ph.langfuse.com https://static.cloudflareinsights.com https://*.stripe.com;
|
||||
style-src 'self' 'unsafe-inline' https://*.crisp.chat;
|
||||
img-src 'self' https: blob: data:;
|
||||
font-src 'self' https://*.crisp.chat;
|
||||
frame-src 'self' https://challenges.cloudflare.com https://*.stripe.com;
|
||||
worker-src 'self' blob:;
|
||||
object-src 'none';
|
||||
base-uri 'self';
|
||||
form-action 'self';
|
||||
frame-ancestors 'none';
|
||||
${env.LANGFUSE_CSP_ENFORCE_HTTPS === "true" ? "upgrade-insecure-requests; block-all-mixed-content;" : ""}
|
||||
`;
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
transpilePackages: ["@langfuse/shared"],
|
||||
@@ -32,27 +51,48 @@ const nextConfig = {
|
||||
key: "x-frame-options",
|
||||
value: "SAMEORIGIN",
|
||||
},
|
||||
{
|
||||
key: "X-Content-Type-Options",
|
||||
value: "nosniff",
|
||||
},
|
||||
{
|
||||
key: "Referrer-Policy",
|
||||
value: "strict-origin-when-cross-origin",
|
||||
},
|
||||
{
|
||||
key: "Permissions-Policy",
|
||||
value: "autoplay=*, fullscreen=*, microphone=*",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: "/:path((?!api).*)*",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: cspHeader.replace(/\n/g, ""),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Required to check authentication status from langfuse.com
|
||||
...(env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined
|
||||
? [
|
||||
{
|
||||
source: "/api/auth/session",
|
||||
headers: [
|
||||
{
|
||||
key: "Access-Control-Allow-Origin",
|
||||
value: "https://langfuse.com",
|
||||
},
|
||||
{ key: "Access-Control-Allow-Credentials", value: "true" },
|
||||
{ key: "Access-Control-Allow-Methods", value: "GET,POST" },
|
||||
{
|
||||
key: "Access-Control-Allow-Headers",
|
||||
value: "Content-Type, Authorization",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
{
|
||||
source: "/api/auth/session",
|
||||
headers: [
|
||||
{
|
||||
key: "Access-Control-Allow-Origin",
|
||||
value: "https://langfuse.com",
|
||||
},
|
||||
{ key: "Access-Control-Allow-Credentials", value: "true" },
|
||||
{ key: "Access-Control-Allow-Methods", value: "GET,POST" },
|
||||
{
|
||||
key: "Access-Control-Allow-Headers",
|
||||
value: "Content-Type, Authorization",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
},
|
||||
|
||||
+36
-36
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "2.14.4",
|
||||
"version": "2.16.2",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "cp generated/openapi-client/openapi.yml public/openapi-client.yml && cp generated/openapi-server/openapi.yml public/openapi-server.yml",
|
||||
"build": "dotenv -e ../.env -- next build",
|
||||
"copy-openapi-spec": "cp generated/openapi-client/openapi.yml public/openapi-client.yml && cp generated/openapi-server/openapi.yml public/openapi-server.yml",
|
||||
"build": "pnpm run copy-openapi-spec && INLINE_RUNTIME_CHUNK=false dotenv -e ../.env -- next build",
|
||||
"dev": "dotenv -e ../.env -- next dev",
|
||||
"lint": "dotenv -e ../.env -- next lint",
|
||||
"lint:fix": "dotenv -e ../.env -- next lint --fix",
|
||||
@@ -24,16 +24,16 @@
|
||||
"dependencies": {
|
||||
"@anthropic-ai/tokenizer": "^0.0.4",
|
||||
"@aws-sdk/client-s3": "^3.507.0",
|
||||
"@aws-sdk/lib-storage": "^3.515.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.507.0",
|
||||
"@aws-sdk/lib-storage": "^3.540.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.540.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"@heroicons/react": "^2.1.3",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@langchain/anthropic": "^0.1.8",
|
||||
"@langchain/core": "^0.1.49",
|
||||
"@langchain/openai": "^0.0.23",
|
||||
"@langchain/anthropic": "^0.1.10",
|
||||
"@langchain/core": "^0.1.54",
|
||||
"@langchain/openai": "^0.0.26",
|
||||
"@langfuse/shared": "workspace:*",
|
||||
"@marsidev/react-turnstile": "^0.5.3",
|
||||
"@marsidev/react-turnstile": "^0.5.4",
|
||||
"@next-auth/prisma-adapter": "^1.0.7",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
@@ -55,14 +55,14 @@
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@react-email/components": "^0.0.14",
|
||||
"@react-email/components": "^0.0.16",
|
||||
"@react-email/render": "^0.0.12",
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@sentry/nextjs": "^7.102.1",
|
||||
"@sentry/node": "^7.107.0",
|
||||
"@sentry/profiling-node": "^7.102.1",
|
||||
"@sentry/types": "^7.88.0",
|
||||
"@sentry/nextjs": "^7.109.0",
|
||||
"@sentry/node": "^7.109.0",
|
||||
"@sentry/profiling-node": "^7.109.0",
|
||||
"@sentry/types": "^7.109.0",
|
||||
"@t3-oss/env-nextjs": "^0.8.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
@@ -72,36 +72,36 @@
|
||||
"@trpc/next": "^10.45.0",
|
||||
"@trpc/react-query": "^10.45.0",
|
||||
"@trpc/server": "^10.45.0",
|
||||
"ai": "^3.0.11",
|
||||
"ai": "^3.0.18",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"cmdk": "^0.2.1",
|
||||
"core-js": "^3.36.0",
|
||||
"core-js": "^3.36.1",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^3.3.1",
|
||||
"decimal.js": "^10.4.3",
|
||||
"exponential-backoff": "^3.1.1",
|
||||
"js-tiktoken": "^1.0.10",
|
||||
"kysely": "^0.27.3",
|
||||
"langchain": "^0.1.28",
|
||||
"langchain": "^0.1.31",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.330.0",
|
||||
"next": "^14.1.0",
|
||||
"lucide-react": "^0.364.0",
|
||||
"next": "^14.1.4",
|
||||
"next-auth": "^4.24.7",
|
||||
"next-query-params": "^5.0.0",
|
||||
"nodemailer": "^6.9.9",
|
||||
"nodemailer": "^6.9.13",
|
||||
"posthog-js": "^1.105.9",
|
||||
"posthog-node": "^3.6.3",
|
||||
"prisma": "^5.11.0",
|
||||
"prisma": "^5.12.1",
|
||||
"react": "18.2.0",
|
||||
"react-day-picker": "^8.10.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-hook-form": "^7.51.2",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-responsive": "^9.0.2",
|
||||
"react18-json-view": "^0.2.8-canary.1",
|
||||
"sonner": "^1.4.0",
|
||||
"react18-json-view": "^0.2.8-canary.6",
|
||||
"sonner": "^1.4.41",
|
||||
"superjson": "2.2.1",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -115,35 +115,35 @@
|
||||
"@playwright/test": "^1.41.2",
|
||||
"@release-it/bumper": "^6.0.1",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.2.1",
|
||||
"@testing-library/react": "^14.2.2",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/eslint": "^8.56.2",
|
||||
"@types/eslint": "^8.56.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "20.10.5",
|
||||
"@types/nodemailer": "^6.4.14",
|
||||
"@types/react": "^18.2.55",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/react": "^18.2.74",
|
||||
"@types/react-dom": "^18.2.24",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "^14.1.0",
|
||||
"eslint-config-next": "^14.1.4",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"node-mocks-http": "^1.14.1",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"prettier-plugin-tailwindcss": "^0.5.13",
|
||||
"release-it": "^17.1.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.3.3"
|
||||
"tsx": "^4.7.2",
|
||||
"typescript": "^5.4.4"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.13.0"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
openapi*.yml
|
||||
@@ -1,104 +0,0 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: langfuse
|
||||
version: ''
|
||||
paths:
|
||||
/api/public/scores:
|
||||
post:
|
||||
description: Add a score to the database, upserts on id
|
||||
operationId: score_create
|
||||
tags:
|
||||
- Score
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Score'
|
||||
'400':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
'401':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
'403':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
'405':
|
||||
description: ''
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateScoreRequest'
|
||||
components:
|
||||
schemas:
|
||||
CreateScoreRequest:
|
||||
title: CreateScoreRequest
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
format: double
|
||||
observationId:
|
||||
type: string
|
||||
comment:
|
||||
type: string
|
||||
required:
|
||||
- traceId
|
||||
- name
|
||||
- value
|
||||
Score:
|
||||
title: Score
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
traceId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: number
|
||||
format: double
|
||||
observationId:
|
||||
type: string
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
comment:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- traceId
|
||||
- name
|
||||
- value
|
||||
- timestamp
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
// of transactions for performance monitoring.
|
||||
// We recommend adjusting this value in production
|
||||
tracesSampleRate: 0.3,
|
||||
tracesSampleRate: 0.2,
|
||||
|
||||
// Capture Replay for 100% of all sessions,
|
||||
// plus for 100% of sessions with an error
|
||||
|
||||
@@ -7,7 +7,7 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
// of transactions for performance monitoring.
|
||||
// We recommend adjusting this value in production
|
||||
tracesSampleRate: 0.3,
|
||||
tracesSampleRate: 0.2,
|
||||
|
||||
// ...
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
// of transactions for performance monitoring.
|
||||
// We recommend adjusting this value in production
|
||||
tracesSampleRate: 1.0,
|
||||
tracesSampler: (samplingContext: SamplingContext) => {
|
||||
if (
|
||||
samplingContext.request &&
|
||||
@@ -20,9 +19,9 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
|
||||
samplingContext.request.url &&
|
||||
samplingContext.request.url.includes("api/trpc")
|
||||
) {
|
||||
return 1.0;
|
||||
return 0.9;
|
||||
}
|
||||
return 0.2;
|
||||
return 0.15;
|
||||
},
|
||||
|
||||
profilesSampleRate: 0.2, // Profiling sample rate is relative to tracesSampleRate
|
||||
|
||||
@@ -11,6 +11,7 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
|
||||
it("should create and get a dataset", async () => {
|
||||
await makeAPICall("POST", "/api/public/datasets", {
|
||||
name: "dataset-name",
|
||||
description: "dataset-description",
|
||||
});
|
||||
|
||||
const dbDataset = await prisma.dataset.findMany({
|
||||
@@ -29,6 +30,94 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
|
||||
expect(getDataset.status).toBe(200);
|
||||
expect(getDataset.body).toMatchObject({
|
||||
name: "dataset-name",
|
||||
description: "dataset-description",
|
||||
});
|
||||
});
|
||||
|
||||
it("GET datasets", async () => {
|
||||
await makeAPICall("POST", "/api/public/datasets", {
|
||||
name: "dataset-name-1",
|
||||
});
|
||||
|
||||
await makeAPICall("POST", "/api/public/datasets", {
|
||||
name: "dataset-name-2",
|
||||
});
|
||||
|
||||
const datasetItemId = v4();
|
||||
|
||||
await makeAPICall("POST", "/api/public/dataset-items", {
|
||||
datasetName: "dataset-name-2",
|
||||
input: { key: "value" },
|
||||
expectedOutput: { key: "value" },
|
||||
id: datasetItemId,
|
||||
});
|
||||
|
||||
const traceId = v4();
|
||||
const observationId = v4();
|
||||
|
||||
const response = await makeAPICall("POST", "/api/public/ingestion", {
|
||||
batch: [
|
||||
{
|
||||
id: v4(),
|
||||
type: "trace-create",
|
||||
timestamp: new Date().toISOString(),
|
||||
body: {
|
||||
id: traceId,
|
||||
name: "trace-name",
|
||||
userId: "user-1",
|
||||
metadata: { key: "value" },
|
||||
release: "1.0.0",
|
||||
version: "2.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: v4(),
|
||||
type: "observation-create",
|
||||
timestamp: new Date().toISOString(),
|
||||
body: {
|
||||
id: observationId,
|
||||
traceId: traceId,
|
||||
type: "GENERATION",
|
||||
name: "generation-name",
|
||||
startTime: "2021-01-01T00:00:00.000Z",
|
||||
endTime: "2021-01-01T00:00:00.000Z",
|
||||
modelParameters: { key: "value" },
|
||||
input: { key: "value" },
|
||||
metadata: { key: "value" },
|
||||
version: "2.0.0",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(response.status).toBe(207);
|
||||
|
||||
await makeAPICall("POST", "/api/public/dataset-run-items", {
|
||||
datasetItemId: datasetItemId,
|
||||
observationId: observationId,
|
||||
runName: "test-run",
|
||||
metadata: { key: "value" },
|
||||
});
|
||||
|
||||
const getDatasets = await makeAPICall("GET", `/api/public/datasets`);
|
||||
|
||||
expect(getDatasets.status).toBe(200);
|
||||
expect(getDatasets.body).toMatchObject({
|
||||
data: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: "dataset-name-1",
|
||||
items: [],
|
||||
runs: [],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "dataset-name-2",
|
||||
items: [datasetItemId],
|
||||
runs: ["test-run"],
|
||||
}),
|
||||
]),
|
||||
meta: expect.objectContaining({
|
||||
totalItems: 2,
|
||||
page: 1,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -168,25 +257,123 @@ describe("/api/public/datasets and /api/public/dataset-items API Endpoints", ()
|
||||
});
|
||||
expect(response.status).toBe(207);
|
||||
|
||||
const runItem = await makeAPICall("POST", "/api/public/dataset-run-items", {
|
||||
datasetItemId: "dataset-item-id",
|
||||
observationId: observationId,
|
||||
runName: "run-name",
|
||||
metadata: { key: "value" },
|
||||
});
|
||||
const dbRun = await prisma.datasetRuns.findFirst({
|
||||
const runItemObservation = await makeAPICall(
|
||||
"POST",
|
||||
"/api/public/dataset-run-items",
|
||||
{
|
||||
datasetItemId: "dataset-item-id",
|
||||
observationId: observationId,
|
||||
runName: "run-only-observation",
|
||||
runDescription: "run-description",
|
||||
metadata: { key: "value" },
|
||||
},
|
||||
);
|
||||
const dbRunObservation = await prisma.datasetRuns.findFirst({
|
||||
where: {
|
||||
name: "run-name",
|
||||
name: "run-only-observation",
|
||||
},
|
||||
include: {
|
||||
datasetRunItems: true,
|
||||
},
|
||||
});
|
||||
expect(dbRun).not.toBeNull();
|
||||
expect(dbRun?.datasetId).toBe(dataset.body.id);
|
||||
expect(dbRun?.metadata).toMatchObject({ key: "value" });
|
||||
expect(runItem.status).toBe(200);
|
||||
expect(runItem.body).toMatchObject({
|
||||
expect(dbRunObservation).not.toBeNull();
|
||||
expect(dbRunObservation?.datasetId).toBe(dataset.body.id);
|
||||
expect(dbRunObservation?.metadata).toMatchObject({ key: "value" });
|
||||
expect(dbRunObservation?.description).toBe("run-description");
|
||||
expect(runItemObservation.status).toBe(200);
|
||||
expect(dbRunObservation?.datasetRunItems[0]).toMatchObject({
|
||||
datasetItemId: "dataset-item-id",
|
||||
observationId: observationId,
|
||||
datasetRunId: dbRun?.id,
|
||||
traceId: traceId,
|
||||
});
|
||||
|
||||
const getRunAPI = await makeAPICall(
|
||||
"GET",
|
||||
`/api/public/datasets/dataset-name/runs/run-only-observation`,
|
||||
);
|
||||
expect(getRunAPI.status).toBe(200);
|
||||
expect(getRunAPI.body).toMatchObject({
|
||||
name: "run-only-observation",
|
||||
description: "run-description",
|
||||
metadata: { key: "value" },
|
||||
datasetId: dataset.body.id,
|
||||
datasetName: "dataset-name",
|
||||
datasetRunItems: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
datasetItemId: "dataset-item-id",
|
||||
observationId: observationId,
|
||||
traceId: traceId,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
const runItemTrace = await makeAPICall(
|
||||
"POST",
|
||||
"/api/public/dataset-run-items",
|
||||
{
|
||||
datasetItemId: "dataset-item-id",
|
||||
traceId: traceId,
|
||||
runName: "run-only-trace",
|
||||
metadata: { key: "value" },
|
||||
},
|
||||
);
|
||||
const dbRunTrace = await prisma.datasetRuns.findFirst({
|
||||
where: {
|
||||
name: "run-only-trace",
|
||||
},
|
||||
include: {
|
||||
datasetRunItems: true,
|
||||
},
|
||||
});
|
||||
expect(dbRunTrace).not.toBeNull();
|
||||
expect(dbRunTrace?.datasetId).toBe(dataset.body.id);
|
||||
expect(dbRunTrace?.metadata).toMatchObject({ key: "value" });
|
||||
expect(runItemTrace.status).toBe(200);
|
||||
expect(dbRunTrace?.datasetRunItems[0]).toMatchObject({
|
||||
datasetItemId: "dataset-item-id",
|
||||
traceId: traceId,
|
||||
observationId: null,
|
||||
});
|
||||
|
||||
const runItemBoth = await makeAPICall(
|
||||
"POST",
|
||||
"/api/public/dataset-run-items",
|
||||
{
|
||||
datasetItemId: "dataset-item-id",
|
||||
observationId: observationId,
|
||||
traceId: traceId,
|
||||
runName: "run-name-both",
|
||||
metadata: { key: "value" },
|
||||
},
|
||||
);
|
||||
const dbRunBoth = await prisma.datasetRuns.findFirst({
|
||||
where: {
|
||||
name: "run-name-both",
|
||||
},
|
||||
include: {
|
||||
datasetRunItems: true,
|
||||
},
|
||||
});
|
||||
expect(dbRunBoth).not.toBeNull();
|
||||
expect(dbRunBoth?.datasetId).toBe(dataset.body.id);
|
||||
expect(dbRunBoth?.metadata).toMatchObject({ key: "value" });
|
||||
expect(runItemBoth.status).toBe(200);
|
||||
expect(dbRunBoth?.datasetRunItems[0]).toMatchObject({
|
||||
datasetItemId: "dataset-item-id",
|
||||
observationId: observationId,
|
||||
traceId: traceId,
|
||||
});
|
||||
});
|
||||
|
||||
it("dataset-run-items should fail when neither trace nor observation provided", async () => {
|
||||
const response = await makeAPICall(
|
||||
"POST",
|
||||
"/api/public/dataset-run-items",
|
||||
{
|
||||
datasetItemId: "dataset-item-id",
|
||||
runName: "run-fail",
|
||||
},
|
||||
);
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export type ScoresTableRow = {
|
||||
value: number;
|
||||
comment?: string;
|
||||
observationId?: string;
|
||||
traceName: string;
|
||||
traceName?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
@@ -102,15 +102,16 @@ export default function ScoresTable({
|
||||
header: "Observation ID",
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const observationId = row.getValue("observationId");
|
||||
const traceId = row.getValue("traceId");
|
||||
return typeof observationId === "string" &&
|
||||
typeof traceId === "string" ? (
|
||||
const observationId = row.getValue(
|
||||
"observationId",
|
||||
) as ScoresTableRow["observationId"];
|
||||
const traceId = row.getValue("traceId") as ScoresTableRow["traceId"];
|
||||
return traceId && observationId ? (
|
||||
<TableLink
|
||||
path={`/project/${projectId}/traces/${traceId}?observation=${observationId}`}
|
||||
value={observationId}
|
||||
/>
|
||||
) : null;
|
||||
) : undefined;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -120,17 +121,17 @@ export default function ScoresTable({
|
||||
enableHiding: true,
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const value: string = row.getValue("traceName");
|
||||
const value = row.getValue("traceName") as ScoresTableRow["traceName"];
|
||||
const filter = encodeURIComponent(
|
||||
`name;stringOptions;;any of;${value}`,
|
||||
);
|
||||
return (
|
||||
return value ? (
|
||||
<TableLink
|
||||
path={`/project/${projectId}/traces?filter=${value ? filter : ""}`}
|
||||
value={value}
|
||||
truncateAt={40}
|
||||
/>
|
||||
);
|
||||
) : undefined;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -199,7 +200,7 @@ export default function ScoresTable({
|
||||
comment: score.comment ?? undefined,
|
||||
observationId: score.observationId ?? undefined,
|
||||
traceId: score.traceId,
|
||||
traceName: score.traceName,
|
||||
traceName: score.traceName ?? undefined,
|
||||
userId: score.userId ?? undefined,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -124,6 +124,7 @@ export default function TracesTable({
|
||||
filter: filterState,
|
||||
searchQuery,
|
||||
orderBy: orderByState,
|
||||
returnIO: false,
|
||||
};
|
||||
const traces = api.traces.all.useQuery(tracesAllQueryFilter);
|
||||
|
||||
@@ -650,22 +651,8 @@ const TracesIOCell = ({
|
||||
traceId: string;
|
||||
io: "input" | "output";
|
||||
}) => {
|
||||
const trace = api.traces.all.useQuery(
|
||||
{
|
||||
projectId: projectId,
|
||||
filter: [
|
||||
{
|
||||
column: "id",
|
||||
type: "string",
|
||||
operator: "=",
|
||||
value: traceId,
|
||||
},
|
||||
],
|
||||
searchQuery: null,
|
||||
orderBy: null,
|
||||
page: 0,
|
||||
limit: 1,
|
||||
},
|
||||
const trace = api.traces.byId.useQuery(
|
||||
{ traceId: traceId },
|
||||
{
|
||||
enabled: typeof traceId === "string",
|
||||
trpc: {
|
||||
@@ -678,11 +665,7 @@ const TracesIOCell = ({
|
||||
return (
|
||||
<IOCell
|
||||
isLoading={trace.isLoading}
|
||||
data={
|
||||
io === "output"
|
||||
? trace.data?.traces[0]?.output
|
||||
: trace.data?.traces[0]?.input
|
||||
}
|
||||
data={io === "output" ? trace.data?.output : trace.data?.input}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/src/components/ui/table";
|
||||
import { ManualScoreButton } from "@/src/features/manual-scoring/components/ManualScoreButton";
|
||||
import { NewDatasetItemFromObservationButton } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
|
||||
import { NewDatasetItemFromTrace } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
|
||||
import { type ObservationReturnType } from "@/src/server/api/routers/traces";
|
||||
import { api } from "@/src/utils/api";
|
||||
import { IOPreview } from "@/src/components/trace/IOPreview";
|
||||
@@ -128,11 +128,12 @@ export const ObservationPreview = (props: {
|
||||
scores={props.scores}
|
||||
/>
|
||||
{observationWithInputAndOutput.data ? (
|
||||
<NewDatasetItemFromObservationButton
|
||||
<NewDatasetItemFromTrace
|
||||
traceId={preloadedObservation.traceId}
|
||||
observationId={preloadedObservation.id}
|
||||
projectId={props.projectId}
|
||||
observationInput={observationWithInputAndOutput.data.input}
|
||||
observationOutput={observationWithInputAndOutput.data.output}
|
||||
input={observationWithInputAndOutput.data.input}
|
||||
output={observationWithInputAndOutput.data.output}
|
||||
key={preloadedObservation.id}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Badge } from "@/src/components/ui/badge";
|
||||
import { type ObservationReturnType } from "@/src/server/api/routers/traces";
|
||||
import { IOPreview } from "@/src/components/trace/IOPreview";
|
||||
import { formatIntervalSeconds } from "@/src/utils/dates";
|
||||
import { NewDatasetItemFromTrace } from "@/src/features/datasets/components/NewDatasetItemFromObservationButton";
|
||||
|
||||
export const TracePreview = ({
|
||||
trace,
|
||||
@@ -58,11 +59,20 @@ export const TracePreview = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ManualScoreButton
|
||||
projectId={trace.projectId}
|
||||
traceId={trace.id}
|
||||
scores={scores}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<ManualScoreButton
|
||||
projectId={trace.projectId}
|
||||
traceId={trace.id}
|
||||
scores={scores}
|
||||
/>
|
||||
<NewDatasetItemFromTrace
|
||||
traceId={trace.id}
|
||||
projectId={trace.projectId}
|
||||
input={trace.input}
|
||||
output={trace.output}
|
||||
key={trace.id}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<IOPreview
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const VERSION = "v2.14.4";
|
||||
export const VERSION = "v2.16.2";
|
||||
|
||||
@@ -39,6 +39,7 @@ export const env = createEnv({
|
||||
LANGFUSE_DEFAULT_PROJECT_ROLE: z
|
||||
.enum(["ADMIN", "MEMBER", "VIEWER"])
|
||||
.optional(),
|
||||
LANGFUSE_CSP_ENFORCE_HTTPS: z.enum(["true", "false"]).optional(),
|
||||
// AUTH
|
||||
AUTH_GOOGLE_CLIENT_ID: z.string().optional(),
|
||||
AUTH_GOOGLE_CLIENT_SECRET: z.string().optional(),
|
||||
@@ -50,6 +51,14 @@ export const env = createEnv({
|
||||
AUTH_AZURE_AD_CLIENT_SECRET: z.string().optional(),
|
||||
AUTH_AZURE_AD_TENANT_ID: z.string().optional(),
|
||||
AUTH_AZURE_ALLOW_ACCOUNT_LINKING: z.enum(["true", "false"]).optional(),
|
||||
AUTH_OKTA_CLIENT_ID: z.string().optional(),
|
||||
AUTH_OKTA_CLIENT_SECRET: z.string().optional(),
|
||||
AUTH_OKTA_ISSUER: z.string().optional(),
|
||||
AUTH_OKTA_ALLOW_ACCOUNT_LINKING: z.enum(["true", "false"]).optional(),
|
||||
AUTH_AUTH0_CLIENT_ID: z.string().optional(),
|
||||
AUTH_AUTH0_CLIENT_SECRET: z.string().optional(),
|
||||
AUTH_AUTH0_ISSUER: z.string().url().optional(),
|
||||
AUTH_AUTH0_ALLOW_ACCOUNT_LINKING: z.enum(["true", "false"]).optional(),
|
||||
AUTH_DOMAINS_WITH_SSO_ENFORCEMENT: z.string().optional(),
|
||||
AUTH_DISABLE_USERNAME_PASSWORD: z.enum(["true", "false"]).optional(),
|
||||
// EMAIL
|
||||
@@ -107,6 +116,7 @@ export const env = createEnv({
|
||||
LANGFUSE_NEW_USER_SIGNUP_WEBHOOK:
|
||||
process.env.LANGFUSE_NEW_USER_SIGNUP_WEBHOOK,
|
||||
SALT: process.env.SALT,
|
||||
LANGFUSE_CSP_ENFORCE_HTTPS: process.env.LANGFUSE_CSP_ENFORCE_HTTPS,
|
||||
// Default project and role
|
||||
LANGFUSE_DEFAULT_PROJECT_ID: process.env.LANGFUSE_DEFAULT_PROJECT_ID,
|
||||
LANGFUSE_DEFAULT_PROJECT_ROLE: process.env.LANGFUSE_DEFAULT_PROJECT_ROLE,
|
||||
@@ -124,6 +134,14 @@ export const env = createEnv({
|
||||
AUTH_AZURE_AD_TENANT_ID: process.env.AUTH_AZURE_AD_TENANT_ID,
|
||||
AUTH_AZURE_ALLOW_ACCOUNT_LINKING:
|
||||
process.env.AUTH_AZURE_ALLOW_ACCOUNT_LINKING,
|
||||
AUTH_OKTA_CLIENT_ID: process.env.AUTH_OKTA_CLIENT_ID,
|
||||
AUTH_OKTA_CLIENT_SECRET: process.env.AUTH_OKTA_CLIENT_SECRET,
|
||||
AUTH_OKTA_ISSUER: process.env.AUTH_OKTA_ISSUER,
|
||||
AUTH_OKTA_ALLOW_ACCOUNT_LINKING: process.env.AUTH_OKTA_ALLOW_ACCOUNT_LINKING,
|
||||
AUTH_AUTH0_CLIENT_ID: process.env.AUTH_AUTH0_CLIENT_ID,
|
||||
AUTH_AUTH0_CLIENT_SECRET: process.env.AUTH_AUTH0_CLIENT_SECRET,
|
||||
AUTH_AUTH0_ISSUER: process.env.AUTH_AUTH0_ISSUER,
|
||||
AUTH_AUTH0_ALLOW_ACCOUNT_LINKING: process.env.AUTH_AUTH0_ALLOW_ACCOUNT_LINKING,
|
||||
AUTH_DOMAINS_WITH_SSO_ENFORCEMENT:
|
||||
process.env.AUTH_DOMAINS_WITH_SSO_ENFORCEMENT,
|
||||
AUTH_DISABLE_USERNAME_PASSWORD: process.env.AUTH_DISABLE_USERNAME_PASSWORD,
|
||||
|
||||
@@ -30,7 +30,10 @@ export const TabComponent = ({ tabs }: TabComponentProps) => {
|
||||
</div>
|
||||
<div className="hidden sm:block">
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
|
||||
<nav
|
||||
className="-mb-px flex space-x-2 md:space-x-4 lg:space-x-6 xl:space-x-8"
|
||||
aria-label="Tabs"
|
||||
>
|
||||
{tabs.map((tab, index) => (
|
||||
<a
|
||||
key={tab.tabTitle}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DatasetForm } from "@/src/features/datasets/components/DatasetForm";
|
||||
import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
|
||||
|
||||
interface BaseDatasetButtonProps {
|
||||
mode: "create" | "rename" | "delete";
|
||||
mode: "create" | "update" | "delete";
|
||||
projectId: string;
|
||||
className?: string;
|
||||
onFormSuccess?: () => void;
|
||||
@@ -28,16 +28,17 @@ interface DeleteDatasetButtonProps extends BaseDatasetButtonProps {
|
||||
datasetId: string;
|
||||
}
|
||||
|
||||
interface RenameDatasetButtonProps extends BaseDatasetButtonProps {
|
||||
mode: "rename";
|
||||
interface UpdateDatasetButtonProps extends BaseDatasetButtonProps {
|
||||
mode: "update";
|
||||
datasetId: string;
|
||||
datasetName: string;
|
||||
datasetDescription?: string;
|
||||
icon?: boolean;
|
||||
}
|
||||
|
||||
type DatasetActionButtonProps =
|
||||
| CreateDatasetButtonProps
|
||||
| RenameDatasetButtonProps
|
||||
| UpdateDatasetButtonProps
|
||||
| DeleteDatasetButtonProps;
|
||||
|
||||
export const DatasetActionButton = (props: DatasetActionButtonProps) => {
|
||||
@@ -50,7 +51,7 @@ export const DatasetActionButton = (props: DatasetActionButtonProps) => {
|
||||
return (
|
||||
<Dialog open={hasAccess && open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{props.mode === "rename" ? (
|
||||
{props.mode === "update" ? (
|
||||
props.icon ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -99,7 +100,7 @@ export const DatasetActionButton = (props: DatasetActionButtonProps) => {
|
||||
? "Create new dataset"
|
||||
: props.mode === "delete"
|
||||
? "Please confirm"
|
||||
: "Rename dataset"}
|
||||
: "Update dataset"}
|
||||
</DialogTitle>
|
||||
{props.mode === "delete" && (
|
||||
<DialogDescription className="text-md p-0">
|
||||
@@ -123,11 +124,12 @@ export const DatasetActionButton = (props: DatasetActionButtonProps) => {
|
||||
/>
|
||||
) : (
|
||||
<DatasetForm
|
||||
mode="rename"
|
||||
mode="update"
|
||||
projectId={props.projectId}
|
||||
onFormSuccess={() => setOpen(false)}
|
||||
datasetId={props.datasetId}
|
||||
datasetName={props.datasetName}
|
||||
datasetDescription={props.datasetDescription}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { usePostHog } from "posthog-js/react";
|
||||
import { Input } from "@/src/components/ui/input";
|
||||
|
||||
interface BaseDatasetFormProps {
|
||||
mode: "create" | "rename" | "delete";
|
||||
mode: "create" | "update" | "delete";
|
||||
projectId: string;
|
||||
onFormSuccess?: () => void;
|
||||
className?: string;
|
||||
@@ -31,15 +31,16 @@ interface DeleteDatasetFormProps extends BaseDatasetFormProps {
|
||||
datasetId: string;
|
||||
}
|
||||
|
||||
interface RenameDatasetFormProps extends BaseDatasetFormProps {
|
||||
mode: "rename";
|
||||
interface UpdateDatasetFormProps extends BaseDatasetFormProps {
|
||||
mode: "update";
|
||||
datasetId: string;
|
||||
datasetName: string;
|
||||
datasetDescription?: string;
|
||||
}
|
||||
|
||||
type DatasetFormProps =
|
||||
| CreateDatasetFormProps
|
||||
| RenameDatasetFormProps
|
||||
| UpdateDatasetFormProps
|
||||
| DeleteDatasetFormProps;
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -49,6 +50,7 @@ const formSchema = z.object({
|
||||
.refine((name) => name.trim().length > 0, {
|
||||
message: "Input should not be only whitespace",
|
||||
}),
|
||||
description: z.string(),
|
||||
});
|
||||
|
||||
export const DatasetForm = (props: DatasetFormProps) => {
|
||||
@@ -56,9 +58,16 @@ export const DatasetForm = (props: DatasetFormProps) => {
|
||||
const posthog = usePostHog();
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
},
|
||||
defaultValues:
|
||||
props.mode === "update"
|
||||
? {
|
||||
name: props.datasetName,
|
||||
description: props.datasetDescription ?? "",
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
description: "",
|
||||
},
|
||||
});
|
||||
|
||||
const utils = api.useUtils();
|
||||
@@ -70,6 +79,7 @@ export const DatasetForm = (props: DatasetFormProps) => {
|
||||
const trimmedValues = {
|
||||
...values,
|
||||
name: values.name.trim(),
|
||||
description: values.description !== "" ? values.description.trim() : null,
|
||||
};
|
||||
if (props.mode === "create") {
|
||||
posthog.capture("datasets:new_dataset_form_submit");
|
||||
@@ -87,8 +97,8 @@ export const DatasetForm = (props: DatasetFormProps) => {
|
||||
setFormError(error.message);
|
||||
console.error(error);
|
||||
});
|
||||
} else if (props.mode === "rename") {
|
||||
posthog.capture("datasets:rename_dataset_form_submit");
|
||||
} else if (props.mode === "update") {
|
||||
posthog.capture("datasets:update_dataset_form_submit");
|
||||
renameMutation
|
||||
.mutateAsync({
|
||||
...trimmedValues,
|
||||
@@ -134,27 +144,36 @@ export const DatasetForm = (props: DatasetFormProps) => {
|
||||
onSubmit={
|
||||
props.mode === "delete" ? handleDelete : form.handleSubmit(onSubmit)
|
||||
}
|
||||
className="space-y-8"
|
||||
>
|
||||
{props.mode !== "delete" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={
|
||||
props.mode === "rename" ? props.datasetName : ""
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="mb-8 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description (optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -166,7 +185,7 @@ export const DatasetForm = (props: DatasetFormProps) => {
|
||||
? "Create dataset"
|
||||
: props.mode === "delete"
|
||||
? "Delete Dataset"
|
||||
: "Rename dataset"}
|
||||
: "Update dataset"}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DataTable } from "@/src/components/table/data-table";
|
||||
import TableLink from "@/src/components/table/table-link";
|
||||
import { NewDatasetItemButton } from "@/src/features/datasets/components/NewDatasetItemButton";
|
||||
import { api } from "@/src/utils/api";
|
||||
import { type RouterOutput } from "@/src/utils/types";
|
||||
import {
|
||||
@@ -154,7 +153,7 @@ export function DatasetItemsTable({
|
||||
const convertToTableRow = (
|
||||
item: RouterOutput["datasets"]["itemsByDatasetId"]["datasetItems"][number],
|
||||
): RowData => {
|
||||
let input = JSON.stringify(item.input);
|
||||
let input = item.input ? JSON.stringify(item.input) : "";
|
||||
input = input.length > 50 ? input.slice(0, 50) + "..." : input;
|
||||
let expectedOutput = item.expectedOutput
|
||||
? JSON.stringify(item.expectedOutput)
|
||||
@@ -202,11 +201,6 @@ export function DatasetItemsTable({
|
||||
state: paginationState,
|
||||
}}
|
||||
/>
|
||||
<NewDatasetItemButton
|
||||
projectId={projectId}
|
||||
datasetId={datasetId}
|
||||
className="mt-4"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,13 @@ type RowData = {
|
||||
id: string;
|
||||
runAt: string;
|
||||
datasetItemId: string;
|
||||
observation: { id: string; traceId: string };
|
||||
trace?: {
|
||||
traceId: string;
|
||||
observationId?: string;
|
||||
};
|
||||
scores: Score[];
|
||||
latency: number;
|
||||
totalCost: string;
|
||||
latency?: number;
|
||||
totalCost?: string;
|
||||
};
|
||||
|
||||
export function DatasetRunItemsTable(
|
||||
@@ -63,14 +66,21 @@ export function DatasetRunItemsTable(
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "observation",
|
||||
header: "Observation",
|
||||
accessorKey: "trace",
|
||||
header: "Trace",
|
||||
cell: ({ row }) => {
|
||||
const observation: RowData["observation"] = row.getValue("observation");
|
||||
return (
|
||||
const trace: RowData["trace"] = row.getValue("trace");
|
||||
if (!trace) return null;
|
||||
return trace.observationId ? (
|
||||
<TableLink
|
||||
path={`/project/${props.projectId}/traces/${observation.traceId}?observation=${observation.id}`}
|
||||
value={observation.id}
|
||||
path={`/project/${props.projectId}/traces/${trace.traceId}?observation=${trace.observationId}`}
|
||||
value={trace.observationId}
|
||||
truncateAt={7}
|
||||
/>
|
||||
) : (
|
||||
<TableLink
|
||||
path={`/project/${props.projectId}/traces/${trace.traceId}`}
|
||||
value={trace.traceId}
|
||||
truncateAt={7}
|
||||
/>
|
||||
);
|
||||
@@ -81,7 +91,7 @@ export function DatasetRunItemsTable(
|
||||
header: "Latency",
|
||||
cell: ({ row }) => {
|
||||
const latency: RowData["latency"] = row.getValue("latency");
|
||||
return <>{formatIntervalSeconds(latency)}</>;
|
||||
return <>{!!latency ? formatIntervalSeconds(latency) : null}</>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -109,18 +119,17 @@ export function DatasetRunItemsTable(
|
||||
id: item.id,
|
||||
runAt: item.createdAt.toISOString(),
|
||||
datasetItemId: item.datasetItemId,
|
||||
observation: {
|
||||
id: item.observation.id,
|
||||
traceId: item.observation.traceId ?? "", // never actually null, just not enforced by db
|
||||
},
|
||||
scores: item.observation.scores,
|
||||
totalCost: usdFormatter(
|
||||
item.observation.calculatedTotalCost?.toNumber() ?? 0,
|
||||
),
|
||||
latency: intervalInSeconds(
|
||||
item.observation.startTime,
|
||||
item.observation.endTime,
|
||||
),
|
||||
trace: !!item.trace?.id
|
||||
? {
|
||||
traceId: item.trace.id,
|
||||
observationId: item.observation?.id,
|
||||
}
|
||||
: undefined,
|
||||
scores: item.scores,
|
||||
totalCost: !!item.observation?.calculatedTotalCost
|
||||
? usdFormatter(item.observation.calculatedTotalCost.toNumber())
|
||||
: undefined,
|
||||
latency: item.observation?.latency ?? item.trace?.duration ?? undefined,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ type RowData = {
|
||||
avgLatency: number;
|
||||
avgTotalCost: string;
|
||||
scores: RouterOutput["datasets"]["runsByDatasetId"]["runs"][number]["scores"];
|
||||
description: string;
|
||||
metadata: string;
|
||||
};
|
||||
|
||||
@@ -64,8 +65,8 @@ export function DatasetRunsTable(props: {
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: "Created",
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
},
|
||||
{
|
||||
accessorKey: "countRunItems",
|
||||
@@ -104,6 +105,10 @@ export function DatasetRunsTable(props: {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: "Created",
|
||||
},
|
||||
{
|
||||
accessorKey: "metadata",
|
||||
header: "Metadata",
|
||||
@@ -124,6 +129,7 @@ export function DatasetRunsTable(props: {
|
||||
avgLatency: item.avgLatency,
|
||||
avgTotalCost: usdFormatter(item.avgTotalCost.toNumber()),
|
||||
scores: item.scores,
|
||||
description: item.description ?? "",
|
||||
metadata: JSON.stringify(item.metadata),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ type RowData = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
description: string;
|
||||
createdAt: string;
|
||||
lastRunAt?: string;
|
||||
countItems: number;
|
||||
@@ -66,6 +67,10 @@ export function DatasetsTable(props: { projectId: string }) {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
},
|
||||
{
|
||||
accessorKey: "countItems",
|
||||
header: "Items",
|
||||
@@ -99,10 +104,11 @@ export function DatasetsTable(props: { projectId: string }) {
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DatasetActionButton
|
||||
mode="rename"
|
||||
mode="update"
|
||||
projectId={props.projectId}
|
||||
datasetId={key.id}
|
||||
datasetName={key.name}
|
||||
datasetDescription={row.getValue("description") ?? undefined}
|
||||
/>
|
||||
<DatasetActionButton
|
||||
mode="delete"
|
||||
@@ -121,6 +127,7 @@ export function DatasetsTable(props: { projectId: string }) {
|
||||
): RowData => {
|
||||
return {
|
||||
key: { id: item.id, name: item.name },
|
||||
description: item.description ?? "",
|
||||
createdAt: item.createdAt.toLocaleString(),
|
||||
lastRunAt: item.lastRunAt?.toLocaleString() ?? "",
|
||||
countItems: item.countDatasetItems,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
|
||||
const formSchema = z.object({
|
||||
input: z.string().refine(
|
||||
(value) => {
|
||||
if (value === "") return true;
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
@@ -133,7 +134,7 @@ export const EditDatasetItem = ({
|
||||
name="expectedOutput"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Expected output (optional)</FormLabel>
|
||||
<FormLabel>Expected output</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
|
||||
@@ -27,6 +27,7 @@ const formSchema = z.object({
|
||||
datasetId: z.string().min(1, "Select a dataset"),
|
||||
input: z.string().refine(
|
||||
(value) => {
|
||||
if (value === "") return true;
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
@@ -58,9 +59,10 @@ const formSchema = z.object({
|
||||
|
||||
export const NewDatasetItemForm = (props: {
|
||||
projectId: string;
|
||||
traceId?: string;
|
||||
observationId?: string;
|
||||
observationInput?: Prisma.JsonValue;
|
||||
observationOutput?: Prisma.JsonValue;
|
||||
input?: Prisma.JsonValue;
|
||||
output?: Prisma.JsonValue;
|
||||
datasetId?: string;
|
||||
onFormSuccess?: () => void;
|
||||
}) => {
|
||||
@@ -70,12 +72,8 @@ export const NewDatasetItemForm = (props: {
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
datasetId: props.datasetId ?? "",
|
||||
input: props.observationInput
|
||||
? JSON.stringify(props.observationInput, null, 2)
|
||||
: "",
|
||||
expectedOutput: props.observationOutput
|
||||
? JSON.stringify(props.observationOutput, null, 2)
|
||||
: "",
|
||||
input: props.input ? JSON.stringify(props.input, null, 2) : "",
|
||||
expectedOutput: props.output ? JSON.stringify(props.output, null, 2) : "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -91,12 +89,14 @@ export const NewDatasetItemForm = (props: {
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
posthog.capture("datasets:new_dataset_item_form_submit", {
|
||||
hasSourceTrace: !!props.traceId,
|
||||
hasSourceObservation: !!props.observationId,
|
||||
});
|
||||
createDatasetItemMutation
|
||||
.mutateAsync({
|
||||
...values,
|
||||
projectId: props.projectId,
|
||||
sourceTraceId: props.traceId,
|
||||
sourceObservationId: props.observationId,
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -22,23 +22,26 @@ import { type Prisma } from "@langfuse/shared/src/db";
|
||||
import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
export const NewDatasetItemFromObservationButton = (props: {
|
||||
export const NewDatasetItemFromTrace = (props: {
|
||||
projectId: string;
|
||||
observationId: string;
|
||||
observationInput: Prisma.JsonValue;
|
||||
observationOutput: Prisma.JsonValue;
|
||||
traceId: string;
|
||||
observationId?: string;
|
||||
input: Prisma.JsonValue;
|
||||
output: Prisma.JsonValue;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const session = useSession();
|
||||
const observationInDatasets = api.datasets.observationInDatasets.useQuery(
|
||||
{
|
||||
projectId: props.projectId,
|
||||
observationId: props.observationId,
|
||||
},
|
||||
{
|
||||
enabled: session.status === "authenticated",
|
||||
},
|
||||
);
|
||||
const observationInDatasets =
|
||||
api.datasets.datasetItemsBasedOnTraceOrObservation.useQuery(
|
||||
{
|
||||
projectId: props.projectId,
|
||||
traceId: props.traceId,
|
||||
observationId: props.observationId,
|
||||
},
|
||||
{
|
||||
enabled: session.status === "authenticated",
|
||||
},
|
||||
);
|
||||
const hasAccess = useHasAccess({
|
||||
projectId: props.projectId,
|
||||
scope: "datasets:CUD",
|
||||
@@ -103,10 +106,11 @@ export const NewDatasetItemFromObservationButton = (props: {
|
||||
<DialogTitle className="mb-5">Add to dataset</DialogTitle>
|
||||
</DialogHeader>
|
||||
<NewDatasetItemForm
|
||||
traceId={props.traceId}
|
||||
observationId={props.observationId}
|
||||
projectId={props.projectId}
|
||||
observationInput={props.observationInput}
|
||||
observationOutput={props.observationOutput}
|
||||
input={props.input}
|
||||
output={props.output}
|
||||
onFormSuccess={() => setOpen(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
@@ -42,6 +42,7 @@ export const datasetRouter = createTRPCRouter({
|
||||
.select(({ eb }) => [
|
||||
"datasets.id",
|
||||
"datasets.name",
|
||||
"datasets.description",
|
||||
"datasets.created_at as createdAt",
|
||||
"datasets.updated_at as updatedAt",
|
||||
eb.fn.count("dataset_items.id").distinct().as("countDatasetItems"),
|
||||
@@ -179,6 +180,7 @@ export const datasetRouter = createTRPCRouter({
|
||||
SELECT
|
||||
runs.id,
|
||||
runs.name,
|
||||
runs.description,
|
||||
runs.metadata,
|
||||
runs.created_at "createdAt",
|
||||
runs.updated_at "updatedAt",
|
||||
@@ -202,7 +204,8 @@ export const datasetRouter = createTRPCRouter({
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
8,
|
||||
9
|
||||
ORDER BY
|
||||
runs.created_at DESC
|
||||
LIMIT ${input.limit}
|
||||
@@ -292,6 +295,7 @@ export const datasetRouter = createTRPCRouter({
|
||||
datasetItemId: z.string(),
|
||||
input: z.string().optional(),
|
||||
expectedOutput: z.string().optional(),
|
||||
sourceTraceId: z.string().optional(),
|
||||
sourceObservationId: z.string().optional(),
|
||||
status: z.enum(["ACTIVE", "ARCHIVED"]).optional(),
|
||||
}),
|
||||
@@ -312,15 +316,18 @@ export const datasetRouter = createTRPCRouter({
|
||||
},
|
||||
data: {
|
||||
input:
|
||||
input.input !== undefined
|
||||
? (JSON.parse(input.input) as Prisma.InputJsonObject)
|
||||
: undefined,
|
||||
input.input === ""
|
||||
? Prisma.DbNull
|
||||
: input.input !== undefined
|
||||
? (JSON.parse(input.input) as Prisma.InputJsonObject)
|
||||
: undefined,
|
||||
expectedOutput:
|
||||
input.expectedOutput === ""
|
||||
? Prisma.DbNull
|
||||
: input.expectedOutput !== undefined
|
||||
? (JSON.parse(input.expectedOutput) as Prisma.InputJsonObject)
|
||||
: undefined,
|
||||
sourceTraceId: input.sourceTraceId,
|
||||
sourceObservationId: input.sourceObservationId,
|
||||
status: input.status,
|
||||
},
|
||||
@@ -336,7 +343,13 @@ export const datasetRouter = createTRPCRouter({
|
||||
return datasetItem;
|
||||
}),
|
||||
createDataset: protectedProjectProcedure
|
||||
.input(z.object({ projectId: z.string(), name: z.string() }))
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().nullish(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
throwIfNoAccess({
|
||||
session: ctx.session,
|
||||
@@ -346,6 +359,7 @@ export const datasetRouter = createTRPCRouter({
|
||||
const dataset = await ctx.prisma.dataset.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
description: input.description ?? undefined,
|
||||
projectId: input.projectId,
|
||||
},
|
||||
});
|
||||
@@ -366,7 +380,8 @@ export const datasetRouter = createTRPCRouter({
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
datasetId: z.string(),
|
||||
name: z.string(),
|
||||
name: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -381,7 +396,8 @@ export const datasetRouter = createTRPCRouter({
|
||||
projectId: input.projectId,
|
||||
},
|
||||
data: {
|
||||
name: input.name,
|
||||
name: input.name ?? undefined,
|
||||
description: input.description,
|
||||
},
|
||||
});
|
||||
await auditLog({
|
||||
@@ -390,7 +406,6 @@ export const datasetRouter = createTRPCRouter({
|
||||
resourceId: dataset.id,
|
||||
projectId: input.projectId,
|
||||
action: "update",
|
||||
before: { name: input.name },
|
||||
after: dataset,
|
||||
});
|
||||
|
||||
@@ -425,8 +440,9 @@ export const datasetRouter = createTRPCRouter({
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
datasetId: z.string(),
|
||||
input: z.string(),
|
||||
input: z.string().nullish(),
|
||||
expectedOutput: z.string().nullish(),
|
||||
sourceTraceId: z.string().optional(),
|
||||
sourceObservationId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
@@ -448,7 +464,12 @@ export const datasetRouter = createTRPCRouter({
|
||||
|
||||
const datasetItem = await ctx.prisma.datasetItem.create({
|
||||
data: {
|
||||
input: JSON.parse(input.input) as Prisma.InputJsonObject,
|
||||
input:
|
||||
input.input === ""
|
||||
? Prisma.DbNull
|
||||
: !!input.input
|
||||
? (JSON.parse(input.input) as Prisma.InputJsonObject)
|
||||
: undefined,
|
||||
expectedOutput:
|
||||
input.expectedOutput === ""
|
||||
? Prisma.DbNull
|
||||
@@ -456,6 +477,7 @@ export const datasetRouter = createTRPCRouter({
|
||||
? (JSON.parse(input.expectedOutput) as Prisma.InputJsonObject)
|
||||
: undefined,
|
||||
datasetId: input.datasetId,
|
||||
sourceTraceId: input.sourceTraceId,
|
||||
sourceObservationId: input.sourceObservationId,
|
||||
},
|
||||
});
|
||||
@@ -491,14 +513,21 @@ export const datasetRouter = createTRPCRouter({
|
||||
datasetItemId: input.datasetItemId,
|
||||
datasetRun: {
|
||||
dataset: {
|
||||
projectId: input.projectId,
|
||||
projectId: ctx.session.projectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
datasetItem: true,
|
||||
observation: {
|
||||
include: {
|
||||
select: {
|
||||
id: true,
|
||||
scores: true,
|
||||
},
|
||||
},
|
||||
trace: {
|
||||
select: {
|
||||
id: true,
|
||||
scores: true,
|
||||
},
|
||||
},
|
||||
@@ -516,18 +545,16 @@ export const datasetRouter = createTRPCRouter({
|
||||
datasetItemId: input.datasetItemId,
|
||||
datasetRun: {
|
||||
dataset: {
|
||||
projectId: input.projectId,
|
||||
projectId: ctx.session.projectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const observationIds = runItems.map((ri) => ri.observationId);
|
||||
const observationIds = runItems
|
||||
.map((ri) => ri.observation?.id)
|
||||
.filter(Boolean) as string[];
|
||||
const observations = await ctx.prisma.observationView.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
calculatedTotalCost: true,
|
||||
},
|
||||
where: {
|
||||
id: {
|
||||
in: observationIds,
|
||||
@@ -535,15 +562,29 @@ export const datasetRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
const traceIds = runItems
|
||||
.map((ri) => ri.trace?.id)
|
||||
.filter(Boolean) as string[];
|
||||
const traces = await ctx.prisma.traceView.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: traceIds,
|
||||
},
|
||||
projectId: ctx.session.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
const items = runItems.map((ri) => {
|
||||
return {
|
||||
id: ri.id,
|
||||
createdAt: ri.createdAt,
|
||||
datasetItemId: ri.datasetItemId,
|
||||
observation: {
|
||||
...ri.observation,
|
||||
...observations.find((o) => o.id === ri.observationId),
|
||||
},
|
||||
observation: observations.find((o) => o.id === ri.observationId),
|
||||
trace: traces.find((t) => t.id === ri.traceId),
|
||||
scores:
|
||||
// use observation scores if run is linked to an observation, otherwise use all trace scores
|
||||
(!!ri.observationId ? ri.observation?.scores : ri.trace?.scores) ??
|
||||
[],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -552,17 +593,19 @@ export const datasetRouter = createTRPCRouter({
|
||||
runItems: items,
|
||||
};
|
||||
}),
|
||||
observationInDatasets: protectedProjectProcedure
|
||||
datasetItemsBasedOnTraceOrObservation: protectedProjectProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
observationId: z.string(),
|
||||
traceId: z.string(),
|
||||
observationId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return ctx.prisma.datasetItem.findMany({
|
||||
where: {
|
||||
sourceObservationId: input.observationId,
|
||||
sourceTraceId: input.traceId,
|
||||
sourceObservationId: input.observationId ?? null, // null as it should not include observations from the same trace
|
||||
dataset: {
|
||||
projectId: input.projectId,
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isPrismaException } from "@/src/utils/exceptions";
|
||||
|
||||
const CreateDatasetItemSchema = z.object({
|
||||
datasetName: z.string(),
|
||||
input: jsonSchema,
|
||||
input: jsonSchema.nullish(),
|
||||
expectedOutput: jsonSchema.nullish(),
|
||||
id: z.string().nullish(),
|
||||
});
|
||||
@@ -41,10 +41,11 @@ export default async function handler(
|
||||
const itemBody = CreateDatasetItemSchema.parse(req.body);
|
||||
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all")
|
||||
return res.status(403).json({
|
||||
message: "Access denied",
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
// Check access to dataset
|
||||
@@ -68,12 +69,12 @@ export default async function handler(
|
||||
},
|
||||
create: {
|
||||
id,
|
||||
input: itemBody.input,
|
||||
input: itemBody.input ?? undefined,
|
||||
expectedOutput: itemBody.expectedOutput ?? undefined,
|
||||
datasetId: dataset.id,
|
||||
},
|
||||
update: {
|
||||
input: itemBody.input,
|
||||
input: itemBody.input ?? undefined,
|
||||
expectedOutput: itemBody.expectedOutput ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -38,10 +38,11 @@ export default async function handler(
|
||||
const { datasetItemId } = GetDatasetItemQuerySchema.parse(req.query);
|
||||
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all")
|
||||
return res.status(403).json({
|
||||
message: "Access denied",
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
// Check access to dataset
|
||||
|
||||
@@ -6,12 +6,19 @@ import { verifyAuthHeaderAndReturnScope } from "@/src/features/public-api/server
|
||||
import { isPrismaException } from "@/src/utils/exceptions";
|
||||
import { jsonSchema } from "@/src/utils/zod";
|
||||
|
||||
const DatasetRunItemPostSchema = z.object({
|
||||
runName: z.string(),
|
||||
metadata: jsonSchema.nullish(),
|
||||
datasetItemId: z.string(),
|
||||
observationId: z.string(),
|
||||
});
|
||||
const DatasetRunItemPostSchema = z
|
||||
.object({
|
||||
runName: z.string(),
|
||||
runDescription: z.string().nullish(),
|
||||
metadata: jsonSchema.nullish(),
|
||||
datasetItemId: z.string(),
|
||||
observationId: z.string().nullish(),
|
||||
traceId: z.string().nullish(),
|
||||
})
|
||||
.refine((data) => data.observationId || data.traceId, {
|
||||
message: "ObservationId or traceId must be provided",
|
||||
path: ["observationId", "traceId"], // Specify the path of the error
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
@@ -33,8 +40,7 @@ export default async function handler(
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
@@ -43,10 +49,16 @@ export default async function handler(
|
||||
", body:",
|
||||
JSON.stringify(req.body, null, 2),
|
||||
);
|
||||
const { datasetItemId, observationId, runName, metadata } =
|
||||
DatasetRunItemPostSchema.parse(req.body);
|
||||
const {
|
||||
datasetItemId,
|
||||
observationId,
|
||||
traceId,
|
||||
runName,
|
||||
runDescription,
|
||||
metadata,
|
||||
} = DatasetRunItemPostSchema.parse(req.body);
|
||||
|
||||
const item = await prisma.datasetItem.findUnique({
|
||||
const datasetItem = await prisma.datasetItem.findUnique({
|
||||
where: {
|
||||
id: datasetItemId,
|
||||
status: "ACTIVE",
|
||||
@@ -58,48 +70,73 @@ export default async function handler(
|
||||
dataset: true,
|
||||
},
|
||||
});
|
||||
const observation = await prisma.observation.findUnique({
|
||||
where: {
|
||||
id: observationId,
|
||||
projectId: authCheck.scope.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
// Validity of id and access checks
|
||||
if (!item) {
|
||||
if (!datasetItem) {
|
||||
console.error("item not found");
|
||||
return res.status(404).json({
|
||||
message: "Dataset item not found or not active",
|
||||
});
|
||||
}
|
||||
if (!observation) {
|
||||
console.error("observation not found");
|
||||
|
||||
const observation = observationId
|
||||
? await prisma.observation.findUnique({
|
||||
where: {
|
||||
id: observationId,
|
||||
projectId: authCheck.scope.projectId,
|
||||
},
|
||||
})
|
||||
: undefined;
|
||||
if (observationId && !observation) {
|
||||
console.error("Observation not found");
|
||||
return res.status(404).json({
|
||||
message: "Observation not found",
|
||||
});
|
||||
}
|
||||
|
||||
const trace = traceId
|
||||
? await prisma.trace.findUnique({
|
||||
where: { id: traceId, projectId: authCheck.scope.projectId },
|
||||
})
|
||||
: undefined;
|
||||
if (traceId && !trace) {
|
||||
console.error("Trace not found");
|
||||
return res.status(404).json({
|
||||
message: "Trace not found",
|
||||
});
|
||||
}
|
||||
|
||||
// double check, should not be necessary due to zod schema + validations above
|
||||
const saveTraceId = trace?.id ?? observation?.traceId;
|
||||
if (!!!saveTraceId) {
|
||||
console.error("Observation or Trace not found");
|
||||
return res.status(404).json({
|
||||
message: "Observation or Trace not found",
|
||||
});
|
||||
}
|
||||
|
||||
const run = await prisma.datasetRuns.upsert({
|
||||
where: {
|
||||
datasetId_name: {
|
||||
datasetId: item.datasetId,
|
||||
datasetId: datasetItem.datasetId,
|
||||
name: runName,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: runName,
|
||||
datasetId: item.datasetId,
|
||||
description: runDescription ?? undefined,
|
||||
datasetId: datasetItem.datasetId,
|
||||
metadata: metadata ?? undefined,
|
||||
},
|
||||
update: {
|
||||
metadata: metadata ?? undefined,
|
||||
description: runDescription ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const runItem = await prisma.datasetRunItems.create({
|
||||
data: {
|
||||
datasetItemId: datasetItemId,
|
||||
observationId: observationId,
|
||||
traceId: saveTraceId,
|
||||
observationId: observation?.id ?? undefined,
|
||||
datasetRunId: run.id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,9 +4,15 @@ import { cors, runMiddleware } from "@/src/features/public-api/server/cors";
|
||||
import { prisma } from "@langfuse/shared/src/db";
|
||||
import { verifyAuthHeaderAndReturnScope } from "@/src/features/public-api/server/apiAuth";
|
||||
import { isPrismaException } from "@/src/utils/exceptions";
|
||||
import { paginationZod } from "@/src/utils/zod";
|
||||
|
||||
const CreateDatasetSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().nullish(),
|
||||
});
|
||||
|
||||
const GetDatasetsSchema = z.object({
|
||||
...paginationZod,
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -34,13 +40,14 @@ export default async function handler(
|
||||
JSON.stringify(req.body, null, 2),
|
||||
);
|
||||
|
||||
const { name } = CreateDatasetSchema.parse(req.body);
|
||||
const { name, description } = CreateDatasetSchema.parse(req.body);
|
||||
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all")
|
||||
return res.status(403).json({
|
||||
message: "Access denied",
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
const dataset = await prisma.dataset.upsert({
|
||||
@@ -52,12 +59,87 @@ export default async function handler(
|
||||
},
|
||||
create: {
|
||||
name,
|
||||
description: description ?? undefined,
|
||||
projectId: authCheck.scope.projectId,
|
||||
},
|
||||
update: {},
|
||||
update: {
|
||||
description: description ?? null,
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json({ ...dataset, items: [], runs: [] });
|
||||
} else if (req.method === "GET") {
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
const args = GetDatasetsSchema.parse(req.query); // uses query and not body
|
||||
console.log("Trying to get datasets", args);
|
||||
|
||||
const datasets = await prisma.dataset.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
description: true,
|
||||
projectId: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
id: true,
|
||||
datasetItems: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
},
|
||||
datasetRuns: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
projectId: authCheck.scope.projectId,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: args.limit,
|
||||
skip: (args.page - 1) * args.limit,
|
||||
});
|
||||
|
||||
console.log("Found datasets", datasets);
|
||||
|
||||
const totalItems = await prisma.dataset.count({
|
||||
where: {
|
||||
projectId: authCheck.scope.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
data: datasets.map(({ datasetItems, datasetRuns, ...rest }) => ({
|
||||
...rest,
|
||||
items: datasetItems.map(({ id }) => id),
|
||||
runs: datasetRuns.map(({ name }) => name),
|
||||
})),
|
||||
meta: {
|
||||
page: args.page,
|
||||
limit: args.limit,
|
||||
totalItems,
|
||||
totalPages: Math.ceil(totalItems / args.limit),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
res.status(405).json({
|
||||
message: "Method Not Allowed",
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
|
||||
@@ -27,8 +27,7 @@ export default async function handler(
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@ export default async function handler(
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET dataset runs",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
@@ -54,6 +53,11 @@ export default async function handler(
|
||||
},
|
||||
include: {
|
||||
datasetRunItems: true,
|
||||
dataset: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -75,7 +79,12 @@ export default async function handler(
|
||||
message: "Dataset run not found",
|
||||
});
|
||||
|
||||
return res.status(200).json(datasetRuns[0]);
|
||||
const { dataset, ...run } = datasetRuns[0];
|
||||
|
||||
return res.status(200).json({
|
||||
...run,
|
||||
datasetName: dataset.name,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
if (isPrismaException(error)) {
|
||||
|
||||
@@ -33,8 +33,7 @@ export default async function handler(
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
const obj = GetUsageSchema.parse(req.query); // uses query and not body
|
||||
|
||||
@@ -38,8 +38,7 @@ export default async function handler(
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET traces",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { type NextApiRequest, type NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
import { isPrismaException } from "@/src/utils/exceptions";
|
||||
import { stringDate } from "@/src/features/public-api/server/ingestion-api-schema";
|
||||
|
||||
const ObservationsGetSchema = z.object({
|
||||
...paginationZod,
|
||||
@@ -19,6 +20,7 @@ const ObservationsGetSchema = z.object({
|
||||
userId: z.string().nullish(),
|
||||
traceId: z.string().nullish(),
|
||||
parentObservationId: z.string().nullish(),
|
||||
fromStartTime: stringDate,
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -51,8 +53,7 @@ export default async function handler(
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET generations",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,6 +121,10 @@ const getObservation = async (
|
||||
? Prisma.sql`AND o."parent_observation_id" = ${query.parentObservationId}`
|
||||
: Prisma.empty;
|
||||
|
||||
const fromStartTimeCondition = query.fromStartTime
|
||||
? Prisma.sql`AND o."start_time" >= ${query.fromStartTime}::timestamp with time zone at time zone 'UTC'`
|
||||
: Prisma.empty;
|
||||
|
||||
const observations = await prisma.$queryRaw<ObservationView[]>`
|
||||
SELECT
|
||||
o."id",
|
||||
@@ -158,6 +163,7 @@ const getObservation = async (
|
||||
${observationTypeCondition}
|
||||
${traceIdCondition}
|
||||
${parentObservationIdCondition}
|
||||
${fromStartTimeCondition}
|
||||
ORDER by o."start_time" DESC
|
||||
OFFSET ${(query.page - 1) * query.limit}
|
||||
LIMIT ${query.limit}
|
||||
@@ -170,6 +176,7 @@ const getObservation = async (
|
||||
${userIdCondition}
|
||||
${traceIdCondition}
|
||||
${parentObservationIdCondition}
|
||||
${fromStartTimeCondition}
|
||||
`;
|
||||
|
||||
if (count.length !== 1) {
|
||||
|
||||
@@ -46,8 +46,7 @@ export default async function handler(
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET prompts",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,8 +111,7 @@ export default async function handler(
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to POST prompts",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ScoreBody,
|
||||
eventTypes,
|
||||
ingestionBatchEvent,
|
||||
stringDate,
|
||||
} from "@/src/features/public-api/server/ingestion-api-schema";
|
||||
import { v4 } from "uuid";
|
||||
import {
|
||||
@@ -21,6 +22,7 @@ const ScoresGetSchema = z.object({
|
||||
...paginationZod,
|
||||
userId: z.string().nullish(),
|
||||
name: z.string().nullish(),
|
||||
fromTimestamp: stringDate,
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -87,16 +89,22 @@ export default async function handler(
|
||||
try {
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
const obj = ScoresGetSchema.parse(req.query); // uses query and not body
|
||||
|
||||
const skipValue = (obj.page - 1) * obj.limit;
|
||||
const userCondition = Prisma.sql`AND t."user_id" = ${obj.userId}`;
|
||||
const nameCondition = Prisma.sql`AND s."name" = ${obj.name}`;
|
||||
const userCondition = obj.userId
|
||||
? Prisma.sql`AND t."user_id" = ${obj.userId}`
|
||||
: Prisma.empty;
|
||||
const nameCondition = obj.name
|
||||
? Prisma.sql`AND s."name" = ${obj.name}`
|
||||
: Prisma.empty;
|
||||
const fromTimestampCondition = obj.fromTimestamp
|
||||
? Prisma.sql`AND t."timestamp" >= ${obj.fromTimestamp}::timestamp with time zone at time zone 'UTC'`
|
||||
: Prisma.empty;
|
||||
|
||||
const scores = await prisma.$queryRaw<
|
||||
Array<Score & { trace: { userId: string } }>
|
||||
@@ -113,17 +121,21 @@ export default async function handler(
|
||||
FROM "scores" AS s
|
||||
JOIN "traces" AS t ON t.id = s.trace_id
|
||||
WHERE t.project_id = ${authCheck.scope.projectId}
|
||||
${obj.userId ? userCondition : Prisma.empty}
|
||||
${obj.name ? nameCondition : Prisma.empty}
|
||||
${userCondition}
|
||||
${nameCondition}
|
||||
${fromTimestampCondition}
|
||||
ORDER BY t."timestamp" DESC
|
||||
LIMIT ${obj.limit} OFFSET ${skipValue}
|
||||
`);
|
||||
const totalItems = await prisma.score.count({
|
||||
where: {
|
||||
name: obj.name ?? undefined, // optional filter
|
||||
name: obj.name ? obj.name : undefined,
|
||||
timestamp: obj.fromTimestamp
|
||||
? { gte: new Date(obj.fromTimestamp) }
|
||||
: undefined,
|
||||
trace: {
|
||||
projectId: authCheck.scope.projectId,
|
||||
userId: obj.userId ?? undefined, // optional filter
|
||||
userId: obj.userId ? obj.userId : undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -28,8 +28,7 @@ export default async function handler(
|
||||
// END CHECK AUTH
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to DELETE scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,8 +99,7 @@ export default async function handler(
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
@@ -37,8 +37,7 @@ export default async function handler(
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET traces",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import {
|
||||
TraceBody,
|
||||
eventTypes,
|
||||
stringDate,
|
||||
} from "@/src/features/public-api/server/ingestion-api-schema";
|
||||
import { v4 } from "uuid";
|
||||
import { telemetry } from "@/src/features/telemetry";
|
||||
@@ -25,6 +26,7 @@ const GetTracesSchema = z.object({
|
||||
userId: z.string().nullish(),
|
||||
name: z.string().nullish(),
|
||||
tags: z.union([z.array(z.string()), z.string()]).nullish(),
|
||||
fromTimestamp: stringDate,
|
||||
orderBy: z
|
||||
.string() // orderBy=timestamp.asc
|
||||
.nullish()
|
||||
@@ -61,10 +63,11 @@ export default async function handler(
|
||||
JSON.stringify(req.body, null, 2),
|
||||
);
|
||||
|
||||
if (authCheck.scope.accessLevel !== "all")
|
||||
return res.status(403).json({
|
||||
message: "Access denied",
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
const body = TraceBody.parse(req.body);
|
||||
|
||||
@@ -82,8 +85,7 @@ export default async function handler(
|
||||
} else if (req.method === "GET") {
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,6 +106,9 @@ export default async function handler(
|
||||
", ",
|
||||
)}] <@ t."tags"`
|
||||
: Prisma.empty;
|
||||
const fromTimestampCondition = obj.fromTimestamp
|
||||
? Prisma.sql`AND t."timestamp" >= ${obj.fromTimestamp}::timestamp with time zone at time zone 'UTC'`
|
||||
: Prisma.empty;
|
||||
|
||||
const orderByCondition = orderByToPrismaSql(
|
||||
obj.orderBy ?? null,
|
||||
@@ -140,6 +145,7 @@ export default async function handler(
|
||||
${userCondition}
|
||||
${nameCondition}
|
||||
${tagsCondition}
|
||||
${fromTimestampCondition}
|
||||
GROUP BY t.id
|
||||
${orderByCondition}
|
||||
LIMIT ${obj.limit} OFFSET ${skipValue}
|
||||
@@ -147,8 +153,16 @@ export default async function handler(
|
||||
const totalItems = await prisma.trace.count({
|
||||
where: {
|
||||
projectId: authCheck.scope.projectId,
|
||||
name: obj.name ?? undefined,
|
||||
userId: obj.userId ?? undefined,
|
||||
name: obj.name ? obj.name : undefined,
|
||||
userId: obj.userId ? obj.userId : undefined,
|
||||
timestamp: obj.fromTimestamp
|
||||
? { gte: new Date(obj.fromTimestamp) }
|
||||
: undefined,
|
||||
tags: obj.tags
|
||||
? {
|
||||
hasEvery: Array.isArray(obj.tags) ? obj.tags : [obj.tags],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ export default async function handler(
|
||||
// CHECK ACCESS SCOPE
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET traces",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
// END CHECK ACCESS SCOPE
|
||||
|
||||
@@ -30,8 +30,7 @@ export default async function handler(
|
||||
if (req.method === "GET") {
|
||||
if (authCheck.scope.accessLevel !== "all") {
|
||||
return res.status(401).json({
|
||||
message:
|
||||
"Access denied - need to use basic auth with secret key to GET scores",
|
||||
message: "Access denied - need to use basic auth with secret key",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { env } from "@/src/env.mjs";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { FcGoogle } from "react-icons/fc";
|
||||
import { FaGithub } from "react-icons/fa";
|
||||
import { SiOkta, SiAuth0 } from "react-icons/si";
|
||||
import { TbBrandAzure } from "react-icons/tb";
|
||||
import { signIn } from "next-auth/react";
|
||||
import Head from "next/head";
|
||||
@@ -41,7 +42,9 @@ export type PageProps = {
|
||||
credentials: boolean;
|
||||
google: boolean;
|
||||
github: boolean;
|
||||
okta: boolean;
|
||||
azureAd: boolean;
|
||||
auth0: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -57,11 +60,19 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async () => {
|
||||
github:
|
||||
env.AUTH_GITHUB_CLIENT_ID !== undefined &&
|
||||
env.AUTH_GITHUB_CLIENT_SECRET !== undefined,
|
||||
okta:
|
||||
env.AUTH_OKTA_CLIENT_ID !== undefined &&
|
||||
env.AUTH_OKTA_CLIENT_SECRET !== undefined &&
|
||||
env.AUTH_OKTA_ISSUER !== undefined,
|
||||
credentials: env.AUTH_DISABLE_USERNAME_PASSWORD !== "true",
|
||||
azureAd:
|
||||
env.AUTH_AZURE_AD_CLIENT_ID !== undefined &&
|
||||
env.AUTH_AZURE_AD_CLIENT_SECRET !== undefined &&
|
||||
env.AUTH_AZURE_AD_TENANT_ID !== undefined,
|
||||
auth0:
|
||||
env.AUTH_AUTH0_CLIENT_ID !== undefined &&
|
||||
env.AUTH_AUTH0_CLIENT_SECRET !== undefined &&
|
||||
env.AUTH_AUTH0_ISSUER !== undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -118,6 +129,30 @@ export function SSOButtons({
|
||||
{action} with Azure AD
|
||||
</Button>
|
||||
)}
|
||||
{authProviders.okta && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
posthog.capture("sign_in:okta_button_click");
|
||||
void signIn("okta");
|
||||
}}
|
||||
variant="secondary"
|
||||
>
|
||||
<SiOkta className="mr-3" size={18} />
|
||||
{action} with Okta
|
||||
</Button>
|
||||
)}
|
||||
{authProviders.auth0 && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
posthog.capture("sign_in:auth0_button_click");
|
||||
void signIn("auth0");
|
||||
}}
|
||||
variant="secondary"
|
||||
>
|
||||
<SiAuth0 className="mr-3" size={18} />
|
||||
{action} with Auth0
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
@@ -131,7 +166,10 @@ export default function SignIn({ authProviders }: PageProps) {
|
||||
|
||||
const posthog = usePostHog();
|
||||
const [turnstileToken, setTurnstileToken] = useState<string>();
|
||||
const turnstileRef = useRef<any>();
|
||||
// Used to refresh turnstile as the token can only be used once
|
||||
const [turnstileCData, setTurnstileCData] = useState<string>(
|
||||
new Date().getTime().toString(),
|
||||
);
|
||||
|
||||
// Credentials
|
||||
const credentialsForm = useForm<z.infer<typeof credentialAuthForm>>({
|
||||
@@ -157,12 +195,8 @@ export default function SignIn({ authProviders }: PageProps) {
|
||||
setCredentialsFormError(result.error);
|
||||
|
||||
// Refresh turnstile as the token can only be used once
|
||||
if (
|
||||
env.NEXT_PUBLIC_TURNSTILE_SITE_KEY &&
|
||||
turnstileToken &&
|
||||
turnstileRef.current
|
||||
) {
|
||||
turnstileRef.current?.reset();
|
||||
if (env.NEXT_PUBLIC_TURNSTILE_SITE_KEY && turnstileToken) {
|
||||
setTurnstileCData(new Date().getTime().toString());
|
||||
setTurnstileToken(undefined);
|
||||
}
|
||||
}
|
||||
@@ -240,9 +274,12 @@ export default function SignIn({ authProviders }: PageProps) {
|
||||
<>
|
||||
<Divider className="text-gray-400" />
|
||||
<Turnstile
|
||||
ref={turnstileRef}
|
||||
siteKey={env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
|
||||
options={{ theme: "light", action: "sign-in" }}
|
||||
options={{
|
||||
theme: "light",
|
||||
action: "sign-in",
|
||||
cData: turnstileCData,
|
||||
}}
|
||||
className="mx-auto"
|
||||
onSuccess={setTurnstileToken}
|
||||
/>
|
||||
|
||||
@@ -17,11 +17,7 @@ export default function Traces() {
|
||||
href: "https://langfuse.com/docs/datasets",
|
||||
}}
|
||||
actionButtons={
|
||||
<DatasetActionButton
|
||||
projectId={projectId}
|
||||
className="mt-4"
|
||||
mode="create"
|
||||
/>
|
||||
<DatasetActionButton projectId={projectId} mode="create" />
|
||||
}
|
||||
/>
|
||||
<DatasetsTable projectId={projectId} />
|
||||
|
||||
@@ -22,11 +22,18 @@ export default function Dataset() {
|
||||
return (
|
||||
<div>
|
||||
<Header
|
||||
title={`Dataset: ${dataset.data?.name}`}
|
||||
title={dataset.data?.name ?? ""}
|
||||
breadcrumb={[
|
||||
{ name: "Datasets", href: `/project/${projectId}/datasets` },
|
||||
{ name: dataset.data?.name ?? datasetId },
|
||||
]}
|
||||
help={
|
||||
dataset.data?.description
|
||||
? {
|
||||
description: dataset.data.description,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
actionButtons={
|
||||
<>
|
||||
<DetailPageNav
|
||||
@@ -35,10 +42,11 @@ export default function Dataset() {
|
||||
listKey="datasets"
|
||||
/>
|
||||
<DatasetActionButton
|
||||
mode="rename"
|
||||
mode="update"
|
||||
projectId={projectId}
|
||||
datasetId={datasetId}
|
||||
datasetName={dataset.data?.name ?? ""}
|
||||
datasetDescription={dataset.data?.description ?? undefined}
|
||||
icon
|
||||
/>
|
||||
<DeleteButton
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DatasetItemsTable } from "@/src/features/datasets/components/DatasetIte
|
||||
import { DetailPageNav } from "@/src/features/navigate-detail-pages/DetailPageNav";
|
||||
import { DatasetActionButton } from "@/src/features/datasets/components/DatasetActionButton";
|
||||
import { DeleteButton } from "@/src/components/deleteButton";
|
||||
import { NewDatasetItemButton } from "@/src/features/datasets/components/NewDatasetItemButton";
|
||||
|
||||
export default function DatasetItems() {
|
||||
const router = useRouter();
|
||||
@@ -22,7 +23,14 @@ export default function DatasetItems() {
|
||||
return (
|
||||
<div>
|
||||
<Header
|
||||
title={`Dataset: ${dataset.data?.name}`}
|
||||
title={dataset.data?.name ?? ""}
|
||||
help={
|
||||
dataset.data?.description
|
||||
? {
|
||||
description: dataset.data.description,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
breadcrumb={[
|
||||
{ name: "Datasets", href: `/project/${projectId}/datasets` },
|
||||
{
|
||||
@@ -35,16 +43,18 @@ export default function DatasetItems() {
|
||||
]}
|
||||
actionButtons={
|
||||
<>
|
||||
<NewDatasetItemButton projectId={projectId} datasetId={datasetId} />
|
||||
<DetailPageNav
|
||||
currentId={datasetId}
|
||||
path={(id) => `/project/${projectId}/datasets/${id}/items/`}
|
||||
listKey="datasets"
|
||||
/>
|
||||
<DatasetActionButton
|
||||
mode="rename"
|
||||
mode="update"
|
||||
projectId={projectId}
|
||||
datasetId={datasetId}
|
||||
datasetName={dataset.data?.name ?? ""}
|
||||
datasetDescription={dataset.data?.description ?? undefined}
|
||||
icon
|
||||
/>
|
||||
<DeleteButton
|
||||
|
||||
@@ -44,6 +44,12 @@ export default function Dataset() {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{!!run.data?.description && (
|
||||
<>
|
||||
<Header title="Description" level="h3" />
|
||||
<JSONView json={run.data.description} />
|
||||
</>
|
||||
)}
|
||||
{!!run.data?.metadata && (
|
||||
<>
|
||||
<Header title="Metadata" level="h3" />
|
||||
|
||||
@@ -218,7 +218,7 @@ export default function Start() {
|
||||
globalFilterState={mergedFilterState}
|
||||
/>
|
||||
<GenerationLatencyChart
|
||||
className="col-span-1 flex-auto justify-between xl:col-span-full"
|
||||
className="col-span-1 flex-auto justify-between lg:col-span-full"
|
||||
projectId={projectId}
|
||||
agg={agg}
|
||||
globalFilterState={mergedFilterState}
|
||||
|
||||
@@ -227,7 +227,9 @@ export default function UsersPage() {
|
||||
firstEvent:
|
||||
t.firstTrace?.toLocaleString() ?? "No event yet",
|
||||
lastEvent:
|
||||
t.lastObservation?.toLocaleString() ?? "No event yet",
|
||||
t.lastObservation?.toLocaleString() ??
|
||||
t.lastTrace?.toLocaleString() ??
|
||||
"No event yet",
|
||||
totalEvents: compactNumberFormatter(
|
||||
(Number(t.totalTraces) || 0) +
|
||||
(Number(t.totalObservations) || 0),
|
||||
|
||||
@@ -48,7 +48,7 @@ export const scoresRouter = createTRPCRouter({
|
||||
);
|
||||
|
||||
const scores = await ctx.prisma.$queryRaw<
|
||||
Array<Score & { traceName: string; userId: string | null }>
|
||||
Array<Score & { traceName: string | null; userId: string | null }>
|
||||
>(
|
||||
generateScoresQuery(
|
||||
Prisma.sql`
|
||||
|
||||
@@ -34,6 +34,7 @@ const TraceFilterOptions = z.object({
|
||||
searchQuery: z.string().nullable(),
|
||||
filter: z.array(singleFilter).nullable(),
|
||||
orderBy: orderBy,
|
||||
returnIO: z.boolean().default(true),
|
||||
...paginationZod,
|
||||
});
|
||||
|
||||
@@ -48,6 +49,7 @@ export const traceRouter = createTRPCRouter({
|
||||
all: protectedProjectProcedure
|
||||
.input(TraceFilterOptions)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const returnIO = input.returnIO;
|
||||
const filterCondition = tableColumnsToSqlFilterAndPrefix(
|
||||
input.filter ?? [],
|
||||
tracesTableCols,
|
||||
@@ -155,7 +157,17 @@ export const traceRouter = createTRPCRouter({
|
||||
return {
|
||||
traces: traces.map((trace) => {
|
||||
const filteredScores = scores.filter((s) => s.traceId === trace.id);
|
||||
return { ...trace, scores: filteredScores };
|
||||
const { input, output, ...rest } = trace;
|
||||
if (returnIO) {
|
||||
return { ...rest, input, output, scores: filteredScores };
|
||||
} else {
|
||||
return {
|
||||
...rest,
|
||||
input: undefined,
|
||||
output: undefined,
|
||||
scores: filteredScores,
|
||||
};
|
||||
}
|
||||
}),
|
||||
totalCount: totalTraceCount ? Number(totalTraceCount) : undefined,
|
||||
};
|
||||
@@ -209,7 +221,11 @@ export const traceRouter = createTRPCRouter({
|
||||
return res;
|
||||
}),
|
||||
byId: protectedGetTraceProcedure
|
||||
.input(z.object({ traceId: z.string() }))
|
||||
.input(
|
||||
z.object({
|
||||
traceId: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const trace = await ctx.prisma.trace.findFirstOrThrow({
|
||||
where: {
|
||||
|
||||
@@ -17,6 +17,8 @@ import { type Adapter } from "next-auth/adapters";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
import GitHubProvider from "next-auth/providers/github";
|
||||
import OktaProvider from "next-auth/providers/okta";
|
||||
import Auth0Provider from "next-auth/providers/auth0";
|
||||
import AzureADProvider from "next-auth/providers/azure-ad";
|
||||
import { type Provider } from "next-auth/providers/index";
|
||||
import { getCookieName, cookieOptions } from "./utils/cookies";
|
||||
@@ -110,6 +112,36 @@ if (env.AUTH_GOOGLE_CLIENT_ID && env.AUTH_GOOGLE_CLIENT_SECRET)
|
||||
}),
|
||||
);
|
||||
|
||||
if (
|
||||
env.AUTH_OKTA_CLIENT_ID &&
|
||||
env.AUTH_OKTA_CLIENT_SECRET &&
|
||||
env.AUTH_OKTA_ISSUER
|
||||
)
|
||||
providers.push(
|
||||
OktaProvider({
|
||||
clientId: env.AUTH_OKTA_CLIENT_ID,
|
||||
clientSecret: env.AUTH_OKTA_CLIENT_SECRET,
|
||||
issuer: env.AUTH_OKTA_ISSUER,
|
||||
allowDangerousEmailAccountLinking:
|
||||
env.AUTH_OKTA_ALLOW_ACCOUNT_LINKING === "true",
|
||||
}),
|
||||
);
|
||||
|
||||
if (
|
||||
env.AUTH_AUTH0_CLIENT_ID &&
|
||||
env.AUTH_AUTH0_CLIENT_SECRET &&
|
||||
env.AUTH_AUTH0_ISSUER
|
||||
)
|
||||
providers.push(
|
||||
Auth0Provider({
|
||||
clientId: env.AUTH_AUTH0_CLIENT_ID,
|
||||
clientSecret: env.AUTH_AUTH0_CLIENT_SECRET,
|
||||
issuer: env.AUTH_AUTH0_ISSUER,
|
||||
allowDangerousEmailAccountLinking:
|
||||
env.AUTH_AUTH0_ALLOW_ACCOUNT_LINKING === "true",
|
||||
}),
|
||||
);
|
||||
|
||||
if (env.AUTH_GITHUB_CLIENT_ID && env.AUTH_GITHUB_CLIENT_SECRET)
|
||||
providers.push(
|
||||
GitHubProvider({
|
||||
|
||||
+6
-6
@@ -20,17 +20,17 @@
|
||||
"author": "engineering@langfuse.com",
|
||||
"dependencies": {
|
||||
"@langfuse/shared": "workspace:*",
|
||||
"@sentry/node": "^7.107.0",
|
||||
"@sentry/profiling-node": "^7.102.1",
|
||||
"@sentry/node": "^7.109.0",
|
||||
"@sentry/profiling-node": "^7.109.0",
|
||||
"backoff": "^2.5.0",
|
||||
"bullmq": "^5.4.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.3",
|
||||
"express": "^4.19.2",
|
||||
"ioredis": "^5.3.2",
|
||||
"kysely": "^0.27.3",
|
||||
"lodash": "^4.17.21",
|
||||
"pg": "^8.11.3",
|
||||
"pg": "^8.11.5",
|
||||
"pino": "^8.19.0",
|
||||
"pino-http": "^9.0.0",
|
||||
"pino-pretty": "^10.3.1",
|
||||
@@ -45,7 +45,7 @@
|
||||
"@types/express-serve-static-core": "^4.17.43",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/pg": "^8.11.2",
|
||||
"@types/pg": "^8.11.4",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
@@ -57,7 +57,7 @@
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"tsup": "^8.0.2",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript": "^5.4.4",
|
||||
"vitest": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user