diff --git a/package-lock.json b/package-lock.json index 844302e5..50dcf265 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6766,6 +6766,7 @@ "bcrypt": "^5.1.1", "jose": "^5.2.0", "js-tiktoken": "1.0.7", + "jsonrepair": "^3.6.0", "koa": "^2.15.0", "koa-bodyparser": "^4.4.1", "koa-logger": "^3.2.1", diff --git a/packages/backend/package.json b/packages/backend/package.json index c1c622a8..64e742bc 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -23,6 +23,7 @@ "bcrypt": "^5.1.1", "jose": "^5.2.0", "js-tiktoken": "1.0.7", + "jsonrepair": "^3.6.0", "koa": "^2.15.0", "koa-bodyparser": "^4.4.1", "koa-logger": "^3.2.1", diff --git a/packages/backend/src/api/v1/runs/index.ts b/packages/backend/src/api/v1/runs/index.ts index e45121ef..55cddaf6 100644 --- a/packages/backend/src/api/v1/runs/index.ts +++ b/packages/backend/src/api/v1/runs/index.ts @@ -7,6 +7,7 @@ import { fileExport } from "./export" import { deserializeLogic } from "shared" import { convertChecksToSQL } from "@/src/utils/checks" import { checkAccess } from "@/src/utils/authorization" +import { jsonrepair } from "jsonrepair" const runs = new Router({ prefix: "/runs", @@ -55,6 +56,20 @@ function processOutput(output: unknown) { return output } +function processParams(params: any) { + if (!params) return {} + try { + // handles tools received as string (eg. litellm) + if (params.tools && typeof params.tools === "string") { + params.tools = JSON.parse(jsonrepair(params.tools)) + } + } catch (e) { + console.error(e) + console.error("Error parsing tools") + } + return params +} + const formatRun = (run: any) => ({ id: run.id, isPublic: run.isPublic, @@ -79,7 +94,8 @@ const formatRun = (run: any) => ({ error: run.error, status: run.status, siblingRunId: run.siblingRunId, - params: run.params, + params: processParams(run.params), + metadata: run.metadata, user: { id: run.externalUserId, diff --git a/packages/backend/src/api/v1/runs/ingest.ts b/packages/backend/src/api/v1/runs/ingest.ts index 92177812..8d1c8ce0 100644 --- a/packages/backend/src/api/v1/runs/ingest.ts +++ b/packages/backend/src/api/v1/runs/ingest.ts @@ -35,6 +35,7 @@ const registerRunEvent = async ( name, tokensUsage, extra, + params, error, feedback, metadata, @@ -121,8 +122,8 @@ const registerRunEvent = async ( tags, name, status: "started", - params: extra, - metadata: metadata, + params: params || extra, + metadata, templateVersionId: templateId, parentRunId: parentRunIdToUse, input, diff --git a/packages/backend/src/utils/ingest.ts b/packages/backend/src/utils/ingest.ts index bccaf129..01ed2692 100644 --- a/packages/backend/src/utils/ingest.ts +++ b/packages/backend/src/utils/ingest.ts @@ -79,7 +79,7 @@ export const ensureIsUUID = async (id: string): Promise => { } // Converts snake_case to camelCase -// I found some (probably unintended) camelCase props in the tracer events, so normalize everything +// I found some (probably unintended) snake_case props in the tracer events, so normalize everything const recursiveToCamel = (item: any): any => { if (Array.isArray(item)) { return item.map((el: unknown) => recursiveToCamel(el)) @@ -102,25 +102,17 @@ const recursiveToCamel = (item: any): any => { function cleanMetadata(object: any) { if (!object) return undefined + const validTypes = ["string", "number", "boolean"] + return Object.fromEntries( Object.entries(object).map(([key, value]) => { - if ( - typeof value === "string" || - typeof value === "number" || - typeof value === "boolean" - ) { + if (validTypes.includes(typeof value)) { return [key, value] } if (Array.isArray(value)) { return [ key, - value.map((v) => - typeof v === "string" || - typeof v === "number" || - typeof v === "boolean" - ? v - : null, - ), + value.map((v) => (validTypes.includes(typeof value) ? v : null)), ] } return [key, null] diff --git a/packages/frontend/components/blocks/RunInputOutput.tsx b/packages/frontend/components/blocks/RunInputOutput.tsx index c7199fef..8c8ac822 100644 --- a/packages/frontend/components/blocks/RunInputOutput.tsx +++ b/packages/frontend/components/blocks/RunInputOutput.tsx @@ -18,6 +18,7 @@ import { useRun, useUser } from "@/utils/dataHooks" import { notifications } from "@mantine/notifications" import CopyText, { SuperCopyButton } from "./CopyText" import { hasAccess } from "shared" +import ErrorBoundary from "./ErrorBoundary" const isChatMessages = (obj) => { return Array.isArray(obj) @@ -39,30 +40,32 @@ const ParamItem = ({ value: any render?: (value: any) => React.ReactNode color?: string -}) => ( - - {name}: - {render ? ( - render(value) - ) : ( - - {typeof value === "string" || typeof value === "number" ? ( - - {value} - - ) : Array.isArray(value) ? ( - value.map((v, i) => ( - - {v} +}) => { + return ( + + {name}: + {render ? ( + render(value) + ) : ( + + {typeof value === "string" || typeof value === "number" ? ( + + {value} - )) - ) : ( - JSON.stringify(value) - )} - - )} - -) + ) : Array.isArray(value) ? ( + value.map((v, i) => ( + + {v} + + )) + ) : ( + JSON.stringify(value) + )} + + )} + + ) +} // tools format: [ // { @@ -89,8 +92,8 @@ const ParamItem = ({ // } // ] -function RenderTools(tools) { - return tools.map((tool, i) => { +function RenderTools({ tools }) { + return tools?.map((tool, i) => { return ( @@ -117,16 +120,20 @@ function RenderTools(tools) { const PARAMS = [ { key: "temperature", name: "Temperature" }, - { key: "max_tokens", name: "Max tokens" }, - { key: "top_p", name: "Top P" }, - { key: "top_k", name: "Top K" }, - { key: "logit_bias", name: "Logit bias" }, - { key: "presence_penalty", name: "Presence penalty" }, - { key: "frequency_penalty", name: "Frequency penalty" }, + { key: "maxTokens", name: "Max tokens" }, + { key: "topP", name: "Top P" }, + { key: "topK", name: "Top K" }, + { key: "logitBias", name: "Logit bias" }, + { key: "presencePenalty", name: "Presence penalty" }, + { key: "frequencyPenalty", name: "Frequency penalty" }, { key: "stop", name: "Stop" }, { key: "seed", name: "Seed" }, - { key: "tools", name: "Tools", render: RenderTools }, - { key: "tool_choice", name: "Tool Choice" }, + { + key: "tools", + name: "Tools", + render: (value) => , + }, + { key: "toolChoice", name: "Tool Choice" }, ] export default function RunInputOutput({ @@ -145,148 +152,150 @@ export default function RunInputOutput({ hasAccess(user.role, "prompts", "read") return ( - - {run?.type === "llm" && ( - <> - {withShare && ( - - - - Copy{" "} - - {run?.isPublic ? "public" : "private"} - {" "} - URL to share {run?.isPublic ? "" : "with your team"} - - - - {hasAccess(user.role, "logs", "update") && ( - - Make public - - } - checked={run.isPublic} - color={run.isPublic ? "red" : "blue"} - onChange={async (e) => { - const checked = e.currentTarget.checked as boolean - update({ ...run, isPublic: checked }) - if (checked) { - const url = `${window.location.origin}/logs/${run.id}` - await navigator.clipboard.writeText(url) - - notifications.show({ - top: 100, - title: "Run is now public", - message: "Link copied to clipboard", - }) + + + {run?.type === "llm" && ( + <> + {withShare && ( + + + + Copy{" "} + + {run?.isPublic ? "public" : "private"} + {" "} + URL to share {run?.isPublic ? "" : "with your team"} + + + + {hasAccess(user.role, "logs", "update") && ( + + Make public + } - }} - /> - )} - - )} - - - - - ( - - {value} - - )} - /> - - {PARAMS.map( - ({ key, name, render }) => - typeof run.params?.[key] !== "undefined" && ( - - ), - )} + checked={run.isPublic} + color={run.isPublic ? "red" : "blue"} + onChange={async (e) => { + const checked = e.currentTarget.checked as boolean + update({ ...run, isPublic: checked }) + if (checked) { + const url = `${window.location.origin}/logs/${run.id}` + await navigator.clipboard.writeText(url) - {run.tags?.length > 0 && ( - + notifications.show({ + top: 100, + title: "Run is now public", + message: "Link copied to clipboard", + }) + } + }} + /> )} + + )} - {Object.keys(run.metadata || {}).map((key) => ( + + + - typeof value === "string" ? ( - - ) : ( - value.toString() - ) - } + name="Model" + value={run.name} + render={(value) => ( + + {value} + + )} /> - ))} - - {canEnablePlayground && ( - - + {PARAMS.map( + ({ key, name, render }) => + typeof run.params?.[key] !== "undefined" && ( + + ), + )} + + {run.tags?.length > 0 && ( + + )} + + {Object.keys(run.metadata || {}).map((key) => ( + + typeof value === "string" ? ( + + ) : ( + value.toString() + ) + } + /> + ))} - )} - - - - )} - - - Input - - {run?.tokens?.prompt && } - + {canEnablePlayground && ( + + + + )} + + + + )} - + + + Input + + {run?.tokens?.prompt && } + - {(run?.output || run?.error) && ( - <> - - - {run.error - ? "Error" - : run.type === "retriever" - ? "Documents" - : "Output"} - - {run.tokens?.completion && ( - - )} - - - - )} - + + + {(run?.output || run?.error) && ( + <> + + + {run.error + ? "Error" + : run.type === "retriever" + ? "Documents" + : "Output"} + + {run.tokens?.completion && ( + + )} + + + + )} + + ) } diff --git a/packages/frontend/pages/login.tsx b/packages/frontend/pages/login.tsx index 68bcc4fc..4499d673 100644 --- a/packages/frontend/pages/login.tsx +++ b/packages/frontend/pages/login.tsx @@ -55,6 +55,14 @@ function LoginPage() { if (method === "password") { setStep("password") + + // if autofilled, submit the form + if (form.values.password) { + await handleLoginWithPassword({ + email, + password: form.values.password, + }) + } } else if (method === "saml") { setSsoURI(redirect) setStep("saml")