Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac0c13f3e5 | ||
|
|
a7b90fa32c | ||
|
|
9eaf2a2e0f | ||
|
|
61e3cc33bf | ||
|
|
2be198400d | ||
|
|
830df682f9 | ||
|
|
2a420b2e90 | ||
|
|
53127c34ab | ||
|
|
9c57ff4853 | ||
|
|
a71176eda4 | ||
|
|
f59a85f412 |
@@ -70,14 +70,24 @@ types:
|
||||
name: string
|
||||
prompt: list<ChatMessage>
|
||||
config: optional<unknown>
|
||||
labels: optional<list<string>>
|
||||
labels:
|
||||
type: optional<list<string>>
|
||||
docs: List of deployment labels of this prompt version.
|
||||
tags:
|
||||
type: optional<list<string>>
|
||||
docs: List of tags to apply to all versions of this prompt.
|
||||
|
||||
CreateTextPromptRequest:
|
||||
properties:
|
||||
name: string
|
||||
prompt: string
|
||||
config: optional<unknown>
|
||||
labels: optional<list<string>>
|
||||
labels:
|
||||
type: optional<list<string>>
|
||||
docs: List of deployment labels of this prompt version.
|
||||
tags:
|
||||
type: optional<list<string>>
|
||||
docs: List of tags to apply to all versions of this prompt.
|
||||
|
||||
Prompt:
|
||||
union:
|
||||
@@ -89,7 +99,12 @@ types:
|
||||
name: string
|
||||
version: integer
|
||||
config: unknown
|
||||
labels: list<string>
|
||||
labels:
|
||||
type: list<string>
|
||||
docs: List of deployment labels of this prompt version.
|
||||
tags:
|
||||
type: list<string>
|
||||
docs: List of tags. Used to filter via UI and API. The same across versions of a prompt.
|
||||
|
||||
ChatMessage:
|
||||
properties:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langfuse",
|
||||
"version": "2.47.2",
|
||||
"version": "2.47.4",
|
||||
"author": "engineering@langfuse.com",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "2.47.2",
|
||||
"version": "2.47.4",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -2654,6 +2654,13 @@ components:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
description: List of deployment labels of this prompt version.
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
description: List of tags to apply to all versions of this prompt.
|
||||
required:
|
||||
- name
|
||||
- prompt
|
||||
@@ -2672,6 +2679,13 @@ components:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
description: List of deployment labels of this prompt version.
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
description: List of tags to apply to all versions of this prompt.
|
||||
required:
|
||||
- name
|
||||
- prompt
|
||||
@@ -2713,11 +2727,20 @@ components:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of deployment labels of this prompt version.
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: >-
|
||||
List of tags. Used to filter via UI and API. The same across
|
||||
versions of a prompt.
|
||||
required:
|
||||
- name
|
||||
- version
|
||||
- config
|
||||
- labels
|
||||
- tags
|
||||
ChatMessage:
|
||||
title: ChatMessage
|
||||
type: object
|
||||
|
||||
@@ -699,7 +699,7 @@
|
||||
"auth": null,
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"type\": \"chat\",\n \"name\": \"example\",\n \"prompt\": [\n {\n \"role\": \"example\",\n \"content\": \"example\"\n }\n ],\n \"config\": \"UNKNOWN\",\n \"labels\": [\n \"example\"\n ]\n}",
|
||||
"raw": "{\n \"type\": \"chat\",\n \"name\": \"example\",\n \"prompt\": [\n {\n \"role\": \"example\",\n \"content\": \"example\"\n }\n ],\n \"config\": \"UNKNOWN\",\n \"labels\": [\n \"example\"\n ],\n \"tags\": [\n \"example\"\n ]\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
|
||||
@@ -18,7 +18,7 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
|
||||
samplingContext.request.url &&
|
||||
samplingContext.request.url.includes("api/trpc")
|
||||
) {
|
||||
return 0.3;
|
||||
return 0.1;
|
||||
}
|
||||
if (
|
||||
samplingContext.request &&
|
||||
@@ -27,12 +27,12 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN)
|
||||
samplingContext.transactionContext.status !== "ok" &&
|
||||
samplingContext.transactionContext.status !== "unauthenticated"
|
||||
) {
|
||||
return 1;
|
||||
return 0.1;
|
||||
}
|
||||
return 0.1;
|
||||
return 0.01;
|
||||
},
|
||||
|
||||
profilesSampleRate: 0.2, // Profiling sample rate is relative to tracesSampleRate
|
||||
profilesSampleRate: 0.1,
|
||||
integrations: [
|
||||
// Add profiling integration to list of integrations
|
||||
new ProfilingIntegration(),
|
||||
|
||||
@@ -680,6 +680,71 @@ describe("/api/public/v2/prompts API Endpoint", () => {
|
||||
expect(fetchedPrompt.body.createdBy).toBe("API");
|
||||
expect(fetchedPrompt.body.config).toEqual({});
|
||||
});
|
||||
|
||||
it("should update tags across versions", async () => {
|
||||
const promptName = "prompt-name" + nanoid();
|
||||
|
||||
const createPromptVersion = async (tags?: string[]) => {
|
||||
await makeAPICall("POST", baseURI, {
|
||||
name: promptName,
|
||||
prompt: "This is a test prompt",
|
||||
type: PromptType.Text,
|
||||
...(tags !== undefined && { tags: tags }),
|
||||
});
|
||||
};
|
||||
|
||||
const fetchPromptVersion = async (version: number) => {
|
||||
const fetchedPrompt = await makeAPICall(
|
||||
"GET",
|
||||
`${baseURI}/${promptName}?version=${version}`,
|
||||
undefined,
|
||||
);
|
||||
expect(fetchedPrompt.status).toBe(200);
|
||||
if (!isPrompt(fetchedPrompt.body)) {
|
||||
throw new Error("Expected body to be a prompt");
|
||||
}
|
||||
return fetchedPrompt.body;
|
||||
};
|
||||
|
||||
// Create version 1 with ["tag"]
|
||||
await createPromptVersion(["tag"]);
|
||||
let fetchedPrompt1 = await fetchPromptVersion(1);
|
||||
expect(fetchedPrompt1.tags).toEqual(["tag"]);
|
||||
expect(fetchedPrompt1.version).toBe(1);
|
||||
|
||||
// Create version 2 with no tags provided (should use tags from version 1)
|
||||
await createPromptVersion();
|
||||
let fetchedPrompt2 = await fetchPromptVersion(2);
|
||||
expect(fetchedPrompt2.tags).toEqual(["tag"]);
|
||||
expect(fetchedPrompt2.version).toBe(2);
|
||||
|
||||
// Create version 3 with ["tag1", "tag2", "tag3"] (should update tags across versions)
|
||||
await createPromptVersion(["tag1", "tag2", "tag3"]);
|
||||
fetchedPrompt1 = await fetchPromptVersion(1);
|
||||
fetchedPrompt2 = await fetchPromptVersion(2);
|
||||
let fetchedPrompt3 = await fetchPromptVersion(3);
|
||||
expect(fetchedPrompt1.tags).toEqual(["tag1", "tag2", "tag3"]);
|
||||
expect(fetchedPrompt1.version).toBe(1);
|
||||
expect(fetchedPrompt2.tags).toEqual(["tag1", "tag2", "tag3"]);
|
||||
expect(fetchedPrompt2.version).toBe(2);
|
||||
expect(fetchedPrompt3.tags).toEqual(["tag1", "tag2", "tag3"]);
|
||||
expect(fetchedPrompt3.version).toBe(3);
|
||||
|
||||
// remove tags
|
||||
await createPromptVersion([]);
|
||||
fetchedPrompt1 = await fetchPromptVersion(1);
|
||||
fetchedPrompt2 = await fetchPromptVersion(2);
|
||||
fetchedPrompt3 = await fetchPromptVersion(3);
|
||||
let fetchedPrompt4 = await fetchPromptVersion(4);
|
||||
expect(fetchedPrompt1.tags).toEqual([]);
|
||||
expect(fetchedPrompt1.version).toBe(1);
|
||||
expect(fetchedPrompt2.tags).toEqual([]);
|
||||
expect(fetchedPrompt2.version).toBe(2);
|
||||
expect(fetchedPrompt3.tags).toEqual([]);
|
||||
expect(fetchedPrompt3.version).toBe(3);
|
||||
expect(fetchedPrompt4.tags).toEqual([]);
|
||||
expect(fetchedPrompt4.version).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when fetching a prompt list", () => {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const VERSION = "v2.47.2";
|
||||
export const VERSION = "v2.47.4";
|
||||
|
||||
@@ -49,8 +49,7 @@ export const SaveToPromptButton: React.FC = () => {
|
||||
)
|
||||
.data?.prompts.filter((prompt) => prompt.type === PromptType.Chat)
|
||||
.map((prompt) => ({
|
||||
label:
|
||||
prompt.name.slice(0, 20) + (prompt.name.length > 25 ? "..." : ""),
|
||||
label: prompt.name,
|
||||
value: prompt.id,
|
||||
})) ?? [];
|
||||
|
||||
@@ -126,7 +125,9 @@ export const SaveToPromptButton: React.FC = () => {
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{promptName.label}
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{promptName.label}
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandList>
|
||||
|
||||
@@ -28,6 +28,7 @@ import { api } from "@/src/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { JsonEditor } from "@/src/components/json-editor";
|
||||
import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture";
|
||||
import Link from "next/link";
|
||||
|
||||
const formSchema = z.object({
|
||||
modelName: z.string().min(1),
|
||||
@@ -255,11 +256,33 @@ export const NewModelForm = (props: {
|
||||
name="inputPrice"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Input price (USD)</FormLabel>
|
||||
<FormLabel>
|
||||
Input price (USD per{" "}
|
||||
{form.getValues("unit").toLowerCase().replace(/s$/, "")})
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" />
|
||||
</FormControl>
|
||||
<FormDescription>Cost per input unit.</FormDescription>
|
||||
{field.value !== null && field.value !== "" ? (
|
||||
<FormDescription>
|
||||
<ul className="font-mono text-xs">
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 1000).toFixed(4)} USD
|
||||
/ 1k {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 100_000).toFixed(4)}{" "}
|
||||
USD / 100k {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 1_000_000).toFixed(
|
||||
4,
|
||||
)}{" "}
|
||||
USD / 1M {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
</ul>
|
||||
</FormDescription>
|
||||
) : null}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -269,11 +292,33 @@ export const NewModelForm = (props: {
|
||||
name="outputPrice"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Output price (USD)</FormLabel>
|
||||
<FormLabel>
|
||||
Output price (USD per{" "}
|
||||
{form.getValues("unit").toLowerCase().replace(/s$/, "")})
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" />
|
||||
</FormControl>
|
||||
<FormDescription>Cost per output unit.</FormDescription>
|
||||
{field.value !== null && field.value !== "" ? (
|
||||
<FormDescription>
|
||||
<ul className="font-mono text-xs">
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 1000).toFixed(4)} USD
|
||||
/ 1k {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 100_000).toFixed(4)}{" "}
|
||||
USD / 100k {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 1_000_000).toFixed(
|
||||
4,
|
||||
)}{" "}
|
||||
USD / 1M {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
</ul>
|
||||
</FormDescription>
|
||||
) : null}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -283,12 +328,34 @@ export const NewModelForm = (props: {
|
||||
name="totalPrice"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Total price (USD)</FormLabel>
|
||||
<FormLabel>
|
||||
Total price (USD per{" "}
|
||||
{form.getValues("unit").toLowerCase().replace(/s$/, "")})
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Cost per unit, if no separate input/output prices.
|
||||
{field.value !== null && field.value !== "" ? (
|
||||
<ul className="mt-2 font-mono text-xs">
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 1000).toFixed(4)} USD
|
||||
/ 1k {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 100_000).toFixed(4)}{" "}
|
||||
USD / 100k {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
<li>
|
||||
{(parseFloat(field.value ?? "0") * 1_000_000).toFixed(
|
||||
4,
|
||||
)}{" "}
|
||||
USD / 1M {form.getValues("unit").toLowerCase()}
|
||||
</li>
|
||||
</ul>
|
||||
) : (
|
||||
"Enter total price only if no separate input and output prices are provided."
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -328,7 +395,15 @@ export const NewModelForm = (props: {
|
||||
Optionally, Langfuse can tokenize the input and output of a
|
||||
generation if no unit counts are ingested. This is useful for
|
||||
e.g. streamed OpenAI completions. For details on the supported
|
||||
tokenizers, see the docs.
|
||||
tokenizers, see the{" "}
|
||||
<Link
|
||||
href="https://langfuse.com/docs/model-usage-and-cost"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
docs
|
||||
</Link>
|
||||
.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -346,8 +421,15 @@ export const NewModelForm = (props: {
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<FormDescription>
|
||||
The config for the tokenizer. Required for openai. See the
|
||||
docs for details.
|
||||
The config for the tokenizer. Required for openai. See the{" "}
|
||||
<Link
|
||||
href="https://langfuse.com/docs/model-usage-and-cost"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
docs
|
||||
</Link>{" "}
|
||||
for details.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -6,6 +6,8 @@ import { ValidationError } from "@langfuse/shared";
|
||||
import { jsonSchema } from "@/src/utils/zod";
|
||||
import { type PrismaClient } from "@langfuse/shared/src/db";
|
||||
import { LATEST_PROMPT_LABEL } from "@/src/features/prompts/constants";
|
||||
import { removeLabelsFromPreviousPromptVersions } from "@/src/features/prompts/server/utils/updatePromptLabels";
|
||||
import { updatePromptTagsOnAllVersions } from "@/src/features/prompts/server/utils/updatePromptTags";
|
||||
|
||||
export type CreatePromptParams = CreatePromptTRPCType & {
|
||||
createdBy: string;
|
||||
@@ -21,6 +23,7 @@ export const createPrompt = async ({
|
||||
config,
|
||||
createdBy,
|
||||
prisma,
|
||||
tags,
|
||||
}: CreatePromptParams) => {
|
||||
const latestPrompt = await prisma.prompt.findFirst({
|
||||
where: { projectId, name },
|
||||
@@ -35,14 +38,8 @@ export const createPrompt = async ({
|
||||
|
||||
const finalLabels = [...labels, LATEST_PROMPT_LABEL]; // Newly created prompts are always labeled as 'latest'
|
||||
|
||||
const previousLabeledPrompts = await prisma.prompt.findMany({
|
||||
where: {
|
||||
projectId,
|
||||
name,
|
||||
labels: { hasSome: finalLabels },
|
||||
},
|
||||
orderBy: [{ version: "desc" }],
|
||||
});
|
||||
// If tags are undefined, use the tags from the latest prompt version
|
||||
const finalTags = [...new Set(tags ?? latestPrompt?.tags ?? [])];
|
||||
|
||||
const create = [
|
||||
prisma.prompt.create({
|
||||
@@ -52,7 +49,7 @@ export const createPrompt = async ({
|
||||
createdBy,
|
||||
labels: [...new Set(finalLabels)], // Ensure labels are unique
|
||||
type,
|
||||
tags: latestPrompt?.tags,
|
||||
tags: finalTags,
|
||||
version: latestPrompt?.version ? latestPrompt.version + 1 : 1,
|
||||
project: { connect: { id: projectId } },
|
||||
config: jsonSchema.parse(config),
|
||||
@@ -62,18 +59,28 @@ export const createPrompt = async ({
|
||||
|
||||
if (finalLabels.length > 0)
|
||||
// If we're creating a new labeled prompt, we must remove those labels on previous prompts since labels are unique
|
||||
previousLabeledPrompts.forEach((prevPrompt) => {
|
||||
create.push(
|
||||
prisma.prompt.update({
|
||||
where: { id: prevPrompt.id },
|
||||
data: {
|
||||
labels: prevPrompt.labels.filter(
|
||||
(prevLabel) => !finalLabels.includes(prevLabel),
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
create.push(
|
||||
...(await removeLabelsFromPreviousPromptVersions({
|
||||
prisma,
|
||||
projectId,
|
||||
promptName: name,
|
||||
labelsToRemove: finalLabels,
|
||||
})),
|
||||
);
|
||||
|
||||
const haveTagsChanged =
|
||||
JSON.stringify([...new Set(finalTags)].sort()) !==
|
||||
JSON.stringify([...new Set(latestPrompt?.tags)].sort());
|
||||
if (haveTagsChanged)
|
||||
// If we're creating a new prompt with tags, we must update those tags on previous prompts since tags are consistent across versions
|
||||
create.push(
|
||||
...(await updatePromptTagsOnAllVersions({
|
||||
prisma,
|
||||
projectId,
|
||||
promptName: name,
|
||||
tags: finalTags,
|
||||
})),
|
||||
);
|
||||
|
||||
const [createdPrompt] = await prisma.$transaction(create);
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { type PrismaClient } from "@langfuse/shared/src/db";
|
||||
|
||||
export const removeLabelsFromPreviousPromptVersions = async ({
|
||||
prisma,
|
||||
projectId,
|
||||
promptName,
|
||||
labelsToRemove,
|
||||
}: {
|
||||
prisma: PrismaClient;
|
||||
projectId: string;
|
||||
promptName: string;
|
||||
labelsToRemove: string[];
|
||||
}) => {
|
||||
const previouslyLabeledPrompts = await prisma.prompt.findMany({
|
||||
where: {
|
||||
projectId,
|
||||
name: promptName,
|
||||
labels: { hasSome: labelsToRemove },
|
||||
},
|
||||
orderBy: [{ version: "desc" }],
|
||||
});
|
||||
|
||||
return previouslyLabeledPrompts.map((prevPrompt) =>
|
||||
prisma.prompt.update({
|
||||
where: { id: prevPrompt.id },
|
||||
data: {
|
||||
labels: prevPrompt.labels.filter(
|
||||
(prevLabel) => !labelsToRemove.includes(prevLabel),
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { type PrismaClient } from "@langfuse/shared/src/db";
|
||||
|
||||
export const updatePromptTagsOnAllVersions = async ({
|
||||
prisma,
|
||||
projectId,
|
||||
promptName,
|
||||
tags,
|
||||
}: {
|
||||
prisma: PrismaClient;
|
||||
projectId: string;
|
||||
promptName: string;
|
||||
tags: string[];
|
||||
}) => {
|
||||
const previousVersions = await prisma.prompt.findMany({
|
||||
where: { projectId, name: promptName },
|
||||
});
|
||||
|
||||
if (previousVersions.length === 0) return [];
|
||||
|
||||
return previousVersions.map((prevVersion) =>
|
||||
prisma.prompt.update({
|
||||
where: { id: prevVersion.id },
|
||||
data: {
|
||||
tags: [...new Set(tags)], // Ensure tags are unique
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -27,6 +27,7 @@ export const CreateTextPromptSchema = z.object({
|
||||
type: z.literal(PromptType.Text).optional(),
|
||||
prompt: z.string(),
|
||||
config: jsonSchema.nullable().default({}),
|
||||
tags: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const CreateChatPromptSchema = z.object({
|
||||
@@ -35,6 +36,7 @@ export const CreateChatPromptSchema = z.object({
|
||||
type: z.literal(PromptType.Chat),
|
||||
prompt: z.array(ChatMessageSchema),
|
||||
config: jsonSchema.nullable().default({}),
|
||||
tags: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const CreatePromptSchema = z.union([
|
||||
|
||||
@@ -53,10 +53,12 @@ if (
|
||||
setProjectInPosthog();
|
||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
|
||||
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://eu.posthog.com",
|
||||
ui_host: "https://eu.posthog.com",
|
||||
// Enable debug mode in development
|
||||
loaded: (posthog) => {
|
||||
if (process.env.NODE_ENV === "development") posthog.debug();
|
||||
},
|
||||
autocapture: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"crons": [
|
||||
{
|
||||
"path": "/api/cron/ingestion-metrics",
|
||||
"schedule": "*/15 * * * *"
|
||||
"schedule": "0 */6 * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "worker",
|
||||
"version": "2.47.2",
|
||||
"version": "2.47.4",
|
||||
"description": "",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
|
||||
+2
-2
@@ -31,9 +31,9 @@ if (isSentryEnabled) {
|
||||
Sentry.metrics.metricsAggregatorIntegration(),
|
||||
],
|
||||
// Performance Monitoring
|
||||
tracesSampleRate: 0.1, // Capture 100% of the transactions
|
||||
tracesSampleRate: 0.01, // Capture 100% of the transactions
|
||||
// Set sampling rate for profiling - this is relative to tracesSampleRate
|
||||
profilesSampleRate: 0.1,
|
||||
profilesSampleRate: 0.01,
|
||||
sampleRate: 0.1,
|
||||
});
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const VERSION = "v2.47.2";
|
||||
export const VERSION = "v2.47.4";
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { Job, Queue, Worker } from "bullmq";
|
||||
import { BaseError, QueueName, TQueueJobTypes } from "@langfuse/shared";
|
||||
import {
|
||||
ApiError,
|
||||
BaseError,
|
||||
QueueName,
|
||||
TQueueJobTypes,
|
||||
} from "@langfuse/shared";
|
||||
import { evaluate, createEvalJobs } from "../eval-service";
|
||||
import { kyselyPrisma } from "@langfuse/shared/src/db";
|
||||
import logger from "../logger";
|
||||
@@ -59,11 +64,6 @@ export const evalJobExecutor = redis
|
||||
await evaluate({ event: job.data.payload });
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
e,
|
||||
`Failed Evaluation_Execution job for id ${job.data.payload.jobExecutionId} ${e}`
|
||||
);
|
||||
|
||||
const displayError =
|
||||
e instanceof BaseError ? e.message : "An internal error occurred";
|
||||
|
||||
@@ -76,7 +76,20 @@ export const evalJobExecutor = redis
|
||||
.where("project_id", "=", job.data.payload.projectId)
|
||||
.execute();
|
||||
|
||||
Sentry.captureException(e);
|
||||
// do not log expected errors (api failures + missing api keys not provided by the user)
|
||||
if (
|
||||
!(e instanceof ApiError) &&
|
||||
!(
|
||||
e instanceof BaseError &&
|
||||
e.message.includes("API key for provider")
|
||||
)
|
||||
) {
|
||||
logger.error(
|
||||
e,
|
||||
`Failed Evaluation_Execution job for id ${job.data.payload.jobExecutionId} ${e}`
|
||||
);
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user