mirror of
https://github.com/Mintplex-Labs/vector-admin.git
synced 2026-07-21 00:15:23 -04:00
View tests, delete tests, and see test details
This commit is contained in:
@@ -3,12 +3,8 @@ const { Organization } = require("../../../models/organization");
|
||||
const {
|
||||
OrganizationConnection,
|
||||
} = require("../../../models/organizationConnection");
|
||||
const {
|
||||
OrganizationWorkspace,
|
||||
} = require("../../../models/organizationWorkspace");
|
||||
const { Queue } = require("../../../models/queue");
|
||||
const { RagTest } = require("../../../models/ragTest");
|
||||
const { SystemSettings } = require("../../../models/systemSettings");
|
||||
const {
|
||||
userFromSession,
|
||||
validSessionForUser,
|
||||
@@ -20,12 +16,10 @@ const {
|
||||
const {
|
||||
organizationResetJob,
|
||||
} = require("../../../utils/jobs/organizationResetJob");
|
||||
const { OpenAi } = require("../../../utils/openAi");
|
||||
const { createRagTest } = require("../../../utils/toolHelpers/RagTests/create");
|
||||
const {
|
||||
workspaceSimilaritySearch,
|
||||
} = require("../../../utils/toolHelpers/workspaceSimilaritySearch");
|
||||
const { selectConnector } = require("../../../utils/vectordatabases/providers");
|
||||
|
||||
process.env.NODE_ENV === "development"
|
||||
? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
|
||||
@@ -218,10 +212,12 @@ function toolEndpoints(app) {
|
||||
id: true,
|
||||
promptText: true,
|
||||
frequencyType: true,
|
||||
topk: true,
|
||||
topK: true,
|
||||
lastRun: true,
|
||||
comparisons: false,
|
||||
promptVector: false,
|
||||
comparisons: true,
|
||||
promptVector: true,
|
||||
workspace: true,
|
||||
organization: true,
|
||||
organization_rag_test_runs: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `topk` on the `organization_rag_tests` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "organization_rag_tests" DROP COLUMN "topk",
|
||||
ADD COLUMN "topK" INTEGER DEFAULT 3;
|
||||
@@ -152,7 +152,7 @@ model organization_rag_tests {
|
||||
frequencyType String
|
||||
promptText String?
|
||||
promptVector Float[]
|
||||
topk Int? @default(3)
|
||||
topK Int? @default(3)
|
||||
comparisons Json[]
|
||||
lastRun DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
+196
-196
@@ -1551,202 +1551,6 @@
|
||||
"readonly": false
|
||||
}
|
||||
},
|
||||
"organization_rag_tests": {
|
||||
"slug": "organization_rag_tests",
|
||||
"table": {
|
||||
"name": "organization_rag_tests",
|
||||
"pk": "id",
|
||||
"verbose": "organization_rag_tests"
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"verbose": "id",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "frequencyType",
|
||||
"verbose": "frequencyType",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "text",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "promptText",
|
||||
"verbose": "promptText",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "text",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "promptVector",
|
||||
"verbose": "promptVector",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "ARRAY",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "topk",
|
||||
"verbose": "topk",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": true,
|
||||
"defaultValue": "3",
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "comparisons",
|
||||
"verbose": "comparisons",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "ARRAY",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lastRun",
|
||||
"verbose": "lastRun",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "timestamp",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"verbose": "createdAt",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "timestamp",
|
||||
"allowNull": false,
|
||||
"defaultValue": "CURRENT_TIMESTAMP",
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lastUpdatedAt",
|
||||
"verbose": "lastUpdatedAt",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "timestamp",
|
||||
"allowNull": false,
|
||||
"defaultValue": "CURRENT_TIMESTAMP",
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization_id",
|
||||
"verbose": "organization_id",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"verbose": "workspace_id",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"mainview": {
|
||||
"show": true
|
||||
},
|
||||
"listview": {
|
||||
"order": {},
|
||||
"page": 25
|
||||
},
|
||||
"editview": {
|
||||
"readonly": false
|
||||
}
|
||||
},
|
||||
"organization_rag_test_runs": {
|
||||
"slug": "organization_rag_test_runs",
|
||||
"table": {
|
||||
@@ -1878,5 +1682,201 @@
|
||||
"editview": {
|
||||
"readonly": false
|
||||
}
|
||||
},
|
||||
"organization_rag_tests": {
|
||||
"slug": "organization_rag_tests",
|
||||
"table": {
|
||||
"name": "organization_rag_tests",
|
||||
"pk": "id",
|
||||
"verbose": "organization_rag_tests"
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"verbose": "id",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "frequencyType",
|
||||
"verbose": "frequencyType",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "text",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "promptText",
|
||||
"verbose": "promptText",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "text",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "promptVector",
|
||||
"verbose": "promptVector",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "ARRAY",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "comparisons",
|
||||
"verbose": "comparisons",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "ARRAY",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lastRun",
|
||||
"verbose": "lastRun",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "timestamp",
|
||||
"allowNull": true,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"verbose": "createdAt",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "timestamp",
|
||||
"allowNull": false,
|
||||
"defaultValue": "CURRENT_TIMESTAMP",
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lastUpdatedAt",
|
||||
"verbose": "lastUpdatedAt",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "timestamp",
|
||||
"allowNull": false,
|
||||
"defaultValue": "CURRENT_TIMESTAMP",
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization_id",
|
||||
"verbose": "organization_id",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"verbose": "workspace_id",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": false,
|
||||
"defaultValue": null,
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "topK",
|
||||
"verbose": "topK",
|
||||
"control": {
|
||||
"text": true
|
||||
},
|
||||
"type": "integer",
|
||||
"allowNull": true,
|
||||
"defaultValue": "3",
|
||||
"listview": {
|
||||
"show": true
|
||||
},
|
||||
"editview": {
|
||||
"show": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"mainview": {
|
||||
"show": true
|
||||
},
|
||||
"listview": {
|
||||
"order": {},
|
||||
"page": 25
|
||||
},
|
||||
"editview": {
|
||||
"readonly": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,12 +54,12 @@ async function createRagTest(user, request, response) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newRagTest = await RagTest.create(
|
||||
const { test: newRagTest } = await RagTest.create(
|
||||
{
|
||||
frequencyType: frequency,
|
||||
promptText: promptType === "text" ? prompt : null,
|
||||
promptVector: queryVector,
|
||||
topk: Number(topK),
|
||||
topK: Number(topK),
|
||||
comparisons: embeddings,
|
||||
},
|
||||
organization.id,
|
||||
|
||||
@@ -28,7 +28,7 @@ const MigrateConnectionView = lazy(
|
||||
() => import('./pages/Tools/MigrateConnection')
|
||||
);
|
||||
const ResetConnectionView = lazy(() => import('./pages/Tools/ResetConnection'));
|
||||
const RAGDriftTestingView = lazy(() => import('./pages/Tools/RAGDrift'));
|
||||
const RAGTestingView = lazy(() => import('./pages/Tools/RAGTesting'));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -90,8 +90,8 @@ function App() {
|
||||
element={<PrivateRoute Component={ResetConnectionView} />}
|
||||
/>
|
||||
<Route
|
||||
path="/dashboard/:slug/tools/rag-drift"
|
||||
element={<PrivateRoute Component={RAGDriftTestingView} />}
|
||||
path="/dashboard/:slug/tools/rag-testing"
|
||||
element={<PrivateRoute Component={RAGTestingView} />}
|
||||
/>
|
||||
|
||||
<Route path="/auth/sign-up" element={<SignUp />} />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { API_BASE } from '../utils/constants';
|
||||
import { baseHeaders } from '../utils/request';
|
||||
import { IOrganization } from './organization';
|
||||
import { IWorkspace } from './workspace';
|
||||
|
||||
const Tools = {
|
||||
migrateOrg: async (
|
||||
@@ -36,7 +38,7 @@ const Tools = {
|
||||
},
|
||||
ragTests: async (
|
||||
slug: string
|
||||
): Promise<{ ragTests: []; message: null | string }> => {
|
||||
): Promise<{ ragTests: IRagTest[]; message: null | string }> => {
|
||||
return fetch(`${API_BASE}/v1/tools/org/${slug}/rag-tests`, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
@@ -51,7 +53,7 @@ const Tools = {
|
||||
newRAGTest: async (
|
||||
slug: string,
|
||||
settings: object
|
||||
): Promise<{ test: object | null; error: null | string }> => {
|
||||
): Promise<{ test: IRagTest | null; error: null | string }> => {
|
||||
return fetch(`${API_BASE}/v1/tools/org/${slug}/rag-tests/create`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
@@ -64,6 +66,21 @@ const Tools = {
|
||||
return { test: null, error: e.message };
|
||||
});
|
||||
},
|
||||
deleteRagTest: async (test: IRagTest): Promise<boolean> => {
|
||||
return fetch(
|
||||
`${API_BASE}/v1/tools/org/${test.organization.slug}/rag-tests/${test.id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
cache: 'no-cache',
|
||||
headers: baseHeaders(),
|
||||
}
|
||||
)
|
||||
.then((res) => res.ok)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
// Generic Uitls
|
||||
workspaceSimilaritySearch: async (
|
||||
@@ -96,3 +113,28 @@ const Tools = {
|
||||
};
|
||||
|
||||
export default Tools;
|
||||
|
||||
export interface IRagEmbedding {
|
||||
vectorId: string;
|
||||
metadata: object;
|
||||
score: number;
|
||||
}
|
||||
export interface IRagTestRun {
|
||||
id: number;
|
||||
status: 'running' | 'failed' | 'complete' | 'alert';
|
||||
result: object;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface IRagTest {
|
||||
id: number;
|
||||
promptText?: string;
|
||||
promptVector: number[];
|
||||
comparisons: IRagEmbedding[];
|
||||
frequencyType: 'demand' | 'hourly' | 'daily' | 'weekly' | 'monthly';
|
||||
topK: number;
|
||||
lastRun?: string;
|
||||
workspace: IWorkspace;
|
||||
organization: IOrganization;
|
||||
organization_rag_test_runs: IRagTestRun[];
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
+28
-26
@@ -77,7 +77,7 @@ export default function PromptInputAndSearchSubmission({
|
||||
required={true}
|
||||
onChange={debouncedInput}
|
||||
rows={8}
|
||||
className="focus:ring-primary-500 focus:border-primary-500 block w-3/4 rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900"
|
||||
className="focus:ring-primary-500 focus:border-primary-500 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900"
|
||||
placeholder="ex: 'What is VectorAdmin?' or [0.2,0.81,0.89,...,0.05,0.93,0.91,0.17]"
|
||||
></textarea>
|
||||
{!!error && (
|
||||
@@ -126,7 +126,7 @@ function CurrentSimilaritySearch({ organization, prompt, formData }) {
|
||||
if (!formData || !prompt.input || !workspaceId) return null;
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex w-3/4 animate-pulse items-center justify-center rounded-lg bg-gray-100 p-4">
|
||||
<div className="flex w-full animate-pulse items-center justify-center rounded-lg bg-gray-100 p-4">
|
||||
<p className="animate-none text-gray-600">
|
||||
finding top {topK} similar embeddings for {workspaceName}.
|
||||
</p>
|
||||
@@ -136,14 +136,14 @@ function CurrentSimilaritySearch({ organization, prompt, formData }) {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex w-3/4 items-center justify-center rounded-lg bg-red-100 p-4">
|
||||
<div className="flex w-full items-center justify-center rounded-lg bg-red-100 p-4">
|
||||
<p className="text-red-700">{error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (similarEmbeddings.length === 0) {
|
||||
<div className="rounded-lgflex w-3/4 items-center justify-center bg-gray-100 p-4">
|
||||
<div className="rounded-lgflex w-full items-center justify-center bg-gray-100 p-4">
|
||||
<p className="text-gray-600">
|
||||
Could not find any similar embeddings in {workspaceName}.
|
||||
</p>
|
||||
@@ -151,33 +151,35 @@ function CurrentSimilaritySearch({ organization, prompt, formData }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-3/4 flex-col">
|
||||
<div className="flex w-full flex-col">
|
||||
<p className="text-sm text-gray-600">
|
||||
If these results look okay to you - click "Create RAG test" to save this
|
||||
test.
|
||||
</p>
|
||||
{similarEmbeddings.map((embedding, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<input
|
||||
name={`embeddings_${i}`}
|
||||
value={JSON.stringify(embedding)}
|
||||
type="hidden"
|
||||
/>
|
||||
<div className="flex w-full flex-col rounded-lg border border-gray-300 bg-gray-50 p-2 px-4">
|
||||
<div className="flex w-full items-center justify-between border-b border-gray-200 py-1">
|
||||
<p className="text-sm text-gray-600">{embedding.vectorId}</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
Similarity {(embedding.score * 100.0).toFixed(2)}%
|
||||
</p>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{similarEmbeddings.map((embedding, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<input
|
||||
name={`embeddings_${i}`}
|
||||
value={JSON.stringify(embedding)}
|
||||
type="hidden"
|
||||
/>
|
||||
<div className="flex w-full flex-col rounded-lg border border-gray-300 bg-gray-50 p-2 px-4">
|
||||
<div className="flex w-full items-center justify-between border-b border-gray-200 py-1">
|
||||
<p className="text-sm text-gray-600">{embedding.vectorId}</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
Similarity {(embedding.score * 100.0).toFixed(2)}%
|
||||
</p>
|
||||
</div>
|
||||
<pre className="whitespace-break my-2 overflow-scroll text-gray-600">
|
||||
{JSON.stringify(embedding.metadata || {}, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
<pre className="whitespace-break my-2 overflow-scroll text-gray-600">
|
||||
{JSON.stringify(embedding.metadata || {}, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="my-2 w-full rounded-lg bg-blue-600 py-2 text-white hover:bg-blue-700"
|
||||
+54
-5
@@ -6,11 +6,15 @@ import PromptInputAndSearchSubmission from './PromptInputAndSearchSubmission';
|
||||
import { IOrganization } from '../../../../models/organization';
|
||||
|
||||
export default function NewTestForm({
|
||||
title,
|
||||
organization,
|
||||
postCreate,
|
||||
classOverrides,
|
||||
}: {
|
||||
title?: string;
|
||||
organization: IOrganization;
|
||||
postCreate: ([any]: any) => void;
|
||||
postCreate?: ([any]: any) => void;
|
||||
classOverrides?: string;
|
||||
}) {
|
||||
const [formData, setFormData] = useState<FormData | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -38,7 +42,16 @@ export default function NewTestForm({
|
||||
}
|
||||
|
||||
const { test, error } = await Tools.newRAGTest(organization.slug, data);
|
||||
if (!!test) postCreate?.(test);
|
||||
if (!!test)
|
||||
postCreate?.({
|
||||
...test,
|
||||
organization,
|
||||
workspace: {
|
||||
id: form.get('workspaceId'),
|
||||
name: form.get('workspaceName'),
|
||||
},
|
||||
organization_rag_test_runs: [],
|
||||
});
|
||||
if (error) {
|
||||
setError(error);
|
||||
setSaving(false);
|
||||
@@ -47,9 +60,14 @@ export default function NewTestForm({
|
||||
|
||||
const debouncedFormChange = debounce(handleFormChange, 500);
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-gray-200 bg-gray-50/20 p-2">
|
||||
<div
|
||||
className={
|
||||
classOverrides ??
|
||||
'w-full rounded-lg border border-gray-200 bg-gray-50/20 p-2'
|
||||
}
|
||||
>
|
||||
<h2 className="mb-4 text-2xl font-bold text-gray-900">
|
||||
Create your first RAG test
|
||||
{title || 'Create your first RAG test'}
|
||||
</h2>
|
||||
|
||||
{error && (
|
||||
@@ -96,7 +114,7 @@ function FrequencySelection() {
|
||||
<select
|
||||
name="frequency"
|
||||
required={true}
|
||||
className="block w-3/4 rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500"
|
||||
className={`block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500`}
|
||||
>
|
||||
<option value="demand" selected>
|
||||
Do not run automatically
|
||||
@@ -135,3 +153,34 @@ function TopKSelection() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function NewTestFormModal({
|
||||
organization,
|
||||
postCreate,
|
||||
}: {
|
||||
organization: IOrganization;
|
||||
postCreate?: ([any]: any) => void;
|
||||
}) {
|
||||
return (
|
||||
<dialog
|
||||
id={`new-rag-test-modal`}
|
||||
className="my-4 h-auto w-1/2 rounded-lg px-4"
|
||||
>
|
||||
<NewTestForm
|
||||
title="Create a new RAG Test"
|
||||
organization={organization}
|
||||
postCreate={postCreate}
|
||||
classOverrides="w-full overflow-scroll mx-auto p-4"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
document.getElementById('new-rag-test-modal')?.close();
|
||||
}}
|
||||
className="my-2 flex w-full justify-center rounded bg-transparent p-3 font-medium text-slate-500 hover:bg-slate-200"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import { Box, Trash } from 'react-feather';
|
||||
import Tools, { IRagTest } from '../../../../models/tools';
|
||||
import moment from 'moment';
|
||||
import paths from '../../../../utils/paths';
|
||||
import showToast from '../../../../utils/toast';
|
||||
import TestDetailsModal from '../TestDetails';
|
||||
|
||||
export default function RecentTestRuns({
|
||||
tests,
|
||||
setTests,
|
||||
}: {
|
||||
tests: IRagTest[];
|
||||
setTests: (tests: IRagTest[]) => void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<p className="text-xl font-semibold text-gray-800">
|
||||
Recently Run RAG Tests
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
document.getElementById('new-rag-test-modal')?.showModal();
|
||||
}}
|
||||
className="rounded-lg px-2 py-1 text-blue-400 transition-all duration-300 hover:bg-blue-50 hover:text-blue-600"
|
||||
>
|
||||
+ New test
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col">
|
||||
<div className="border-b border-stroke px-4 pb-5 dark:border-strokedark md:px-6 xl:px-7.5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-4/12 ">
|
||||
<span className="font-medium">Target workspace</span>
|
||||
</div>
|
||||
<div className="w-2/12">
|
||||
<span className="font-medium">Last run</span>
|
||||
</div>
|
||||
<div className="w-2/12">
|
||||
<span className="font-medium">Run result</span>
|
||||
</div>
|
||||
<div className="w-4/12 text-center">
|
||||
<span className="font-medium"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tests.map((test) => (
|
||||
<TestItem
|
||||
key={test.id}
|
||||
test={test}
|
||||
onDelete={() => {
|
||||
setTests(tests.filter((t) => t.id !== test.id));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TestItem({ test, onDelete }: { test: IRagTest; onDelete: any }) {
|
||||
const handleRemove = async () => {
|
||||
if (
|
||||
!window.confirm(
|
||||
'Are you sure you want to remove this test? It will remove all of its settings and data'
|
||||
)
|
||||
)
|
||||
return false;
|
||||
const success = await Tools.deleteRagTest(test);
|
||||
if (success) {
|
||||
showToast(`RAG Test for ${test.workspace.name} was deleted`, 'success');
|
||||
onDelete?.();
|
||||
return;
|
||||
}
|
||||
showToast(`RAG Test failed to delete`, 'error');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id={`test-${test.id}`}
|
||||
key={test.id}
|
||||
className="flex w-full items-center gap-5 px-7.5 py-3 text-gray-600 hover:bg-gray-3 dark:hover:bg-meta-4"
|
||||
>
|
||||
<div className="flex w-full items-center gap-3">
|
||||
<div className="flex w-4/12 ">
|
||||
<div className="flex items-center gap-x-1 overflow-x-hidden">
|
||||
<Box className="h-4 w-4" />
|
||||
<span className="font-medium xl:block">
|
||||
{test.workspace.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-2/12 overflow-x-scroll">
|
||||
<span className="font-medium">
|
||||
{test.lastRun ? moment(test.lastRun).fromNow() : '--'}
|
||||
<br />
|
||||
{test.frequencyType !== 'demand' && (
|
||||
<p className="text-xs font-normal italic text-gray-400">
|
||||
Runs {test.frequencyType}
|
||||
</p>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex w-2/12">
|
||||
<TestResultBadge test={test} />
|
||||
</div>
|
||||
<div className="flex w-4/12 items-center justify-between">
|
||||
<button
|
||||
onClick={() => {
|
||||
document
|
||||
.getElementById(`test-${test.id}-details-modal`)
|
||||
?.showModal();
|
||||
}}
|
||||
className="rounded-lg px-2 py-1 text-blue-400 transition-all duration-300 hover:bg-blue-50 hover:text-blue-600"
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
{test.organization_rag_test_runs.length > 0 && (
|
||||
<a
|
||||
href={paths.tools.ragTestRuns(test.organization.slug, test.id)}
|
||||
className="rounded-lg px-2 py-1 text-gray-400 transition-all duration-300 hover:bg-gray-50 hover:text-gray-600"
|
||||
>
|
||||
View Runs
|
||||
</a>
|
||||
)}
|
||||
<button className="rounded-lg px-2 py-1 text-orange-400 transition-all duration-300 hover:bg-orange-600 hover:text-white">
|
||||
Run now
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRemove}
|
||||
className="rounded-lg px-2 py-1 text-red-400 transition-all duration-300 hover:bg-red-100 hover:text-red-600"
|
||||
>
|
||||
<Trash size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TestDetailsModal test={test} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TestResultBadge({ test }: { test: IRagTest }) {
|
||||
const { organization_rag_test_runs = [] } = test;
|
||||
if (organization_rag_test_runs.length === 0) {
|
||||
return (
|
||||
<span className="inline-block rounded bg-gray-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-gray-500">
|
||||
Unknown
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const recentRun = organization_rag_test_runs[0];
|
||||
switch (recentRun.status) {
|
||||
case 'running':
|
||||
return (
|
||||
<span className="inline-block rounded bg-blue-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-blue-500">
|
||||
Running
|
||||
</span>
|
||||
);
|
||||
case 'failed':
|
||||
return (
|
||||
<span className="inline-block rounded bg-red-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-red-500">
|
||||
Exited
|
||||
</span>
|
||||
);
|
||||
case 'complete':
|
||||
return (
|
||||
<span className="inline-block rounded bg-green-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-green-500">
|
||||
Passed
|
||||
</span>
|
||||
);
|
||||
case 'alert':
|
||||
return (
|
||||
<span className="inline-block rounded bg-orange-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-orange-500">
|
||||
Drift detected
|
||||
</span>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<span className="inline-block rounded bg-yellow-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-yellow-500">
|
||||
Unknown
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { IRagTest } from '../../../../models/tools';
|
||||
|
||||
export default function TestDetailsModal({ test }: { test: IRagTest }) {
|
||||
return (
|
||||
<dialog
|
||||
id={`test-${test.id}-details-modal`}
|
||||
className="my-4 h-fit w-1/2 rounded-lg px-4"
|
||||
>
|
||||
<TestDetails test={test} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
document.getElementById(`test-${test.id}-details-modal`)?.close();
|
||||
}}
|
||||
className="my-2 flex w-full justify-center rounded bg-transparent p-3 font-medium text-slate-500 hover:bg-slate-200"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function TestDetails({ test }: { test: IRagTest }) {
|
||||
return (
|
||||
<div className="mx-auto w-full overflow-scroll p-4">
|
||||
<h2 className="mb-4 text-2xl font-bold text-gray-900">
|
||||
Test settings for Test #{test.id}
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<div className="flex flex-col gap-y-6">
|
||||
<FrequencySelection test={test} />
|
||||
<WorkspaceSelection test={test} />
|
||||
<TopKSelection test={test} />
|
||||
<PromptSelection test={test} />
|
||||
<EmbeddingSample test={test} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FrequencySelection({ test }: { test: IRagTest }) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-2 w-full ">
|
||||
<label className="block text-sm font-medium text-gray-900">
|
||||
Testing frequency
|
||||
</label>
|
||||
<p className="text-sm text-gray-600">
|
||||
The frequency in which this test is running.
|
||||
</p>
|
||||
</div>
|
||||
<select
|
||||
name="frequency"
|
||||
disabled={true}
|
||||
value={test.frequencyType}
|
||||
className={`block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500`}
|
||||
>
|
||||
<option value="demand">Do not run automatically</option>
|
||||
<option value="hourly">Hourly</option>
|
||||
<option value="daily">Once daily (6PM)</option>
|
||||
<option value="weekly">Once weekly (Wed @ 6PM)</option>
|
||||
<option value="monthly">Once monthly (15th)</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WorkspaceSelection({ test }: { test: IRagTest }) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-2 w-full ">
|
||||
<label className="block text-sm font-medium text-gray-900">
|
||||
Workspace to test
|
||||
</label>
|
||||
<p className="text-sm text-gray-600">
|
||||
This is the name of the workspace you are running tests against.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="input"
|
||||
disabled={true}
|
||||
className="focus:ring-primary-600 focus:border-primary-600 block w-1/2 rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900"
|
||||
placeholder="Search workspace name"
|
||||
autoComplete="off"
|
||||
value={test.workspace.name}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TopKSelection({ test }: { test: IRagTest }) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-2 w-full ">
|
||||
<label className="block text-sm font-medium text-gray-900">
|
||||
TopK to test
|
||||
</label>
|
||||
<p className="text-sm text-gray-600">
|
||||
The number of embeddings to reference for each test run.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
name="topK"
|
||||
disabled={true}
|
||||
readOnly={true}
|
||||
value={test.topK}
|
||||
className="focus:ring-primary-600 focus:border-primary-600 block w-fit rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-lg text-gray-900"
|
||||
placeholder="TopK embeddings to test"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PromptSelection({ test }: { test: IRagTest }) {
|
||||
const [selection, setSelection] = useState(
|
||||
test.promptText ? 'text' : 'vector'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-2 w-full ">
|
||||
<label className="block text-sm font-medium text-gray-900">
|
||||
Using this input for similarity search
|
||||
</label>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="text-sm text-gray-600">
|
||||
This is the information that is being used for comparison analysis.
|
||||
</p>
|
||||
<select
|
||||
value={selection}
|
||||
onChange={(e) => setSelection(e.target.value)}
|
||||
className="rounded-lg border border-gray-100 p-2 text-sm text-gray-600"
|
||||
>
|
||||
<option value="text">Showing as text</option>
|
||||
<option value="vector">Showing as vector</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
name="prompt"
|
||||
required={true}
|
||||
disabled={true}
|
||||
rows={8}
|
||||
className="focus:ring-primary-500 focus:border-primary-500 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900"
|
||||
placeholder="ex: 'What is VectorAdmin?' or [0.2,0.81,0.89,...,0.05,0.93,0.91,0.17]"
|
||||
value={
|
||||
selection === 'text'
|
||||
? test.promptText
|
||||
: JSON.stringify(test.promptVector)
|
||||
}
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EmbeddingSample({ test }: { test: IRagTest }) {
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<p className="text-sm text-gray-600">
|
||||
These are the samples being compared to for each run.
|
||||
</p>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{test.comparisons.map((embedding, i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<input
|
||||
name={`embeddings_${i}`}
|
||||
value={JSON.stringify(embedding)}
|
||||
type="hidden"
|
||||
/>
|
||||
<div className="flex w-full flex-col rounded-lg border border-gray-300 bg-gray-50 p-2 px-4">
|
||||
<div className="flex w-full items-center justify-between border-b border-gray-200 py-1">
|
||||
<p className="text-sm text-gray-600">{embedding.vectorId}</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
Original Similarity {(embedding.score * 100.0).toFixed(2)}%
|
||||
</p>
|
||||
</div>
|
||||
<pre className="whitespace-break my-2 overflow-scroll text-gray-600">
|
||||
{JSON.stringify(embedding.metadata || {}, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+16
-6
@@ -7,8 +7,9 @@ import paths from '../../../utils/paths';
|
||||
import AppLayout from '../../../layout/AppLayout';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Organization, { IOrganization } from '../../../models/organization';
|
||||
import Tools from '../../../models/tools';
|
||||
import NewTestForm from './NewTestForm';
|
||||
import Tools, { IRagTest } from '../../../models/tools';
|
||||
import NewTestForm, { NewTestFormModal } from './NewTestForm';
|
||||
import RecentTestRuns from './RecentTests';
|
||||
|
||||
export default function RAGDriftTesting() {
|
||||
const { user } = useUser();
|
||||
@@ -16,7 +17,7 @@ export default function RAGDriftTesting() {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [organizations, setOrganizations] = useState<IOrganization[]>([]);
|
||||
const [organization, setOrganization] = useState<IOrganization | null>(null);
|
||||
const [ragTests, setRagTests] = useState<object[]>([]);
|
||||
const [ragTests, setRagTests] = useState<IRagTest[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function userOrgs() {
|
||||
@@ -87,12 +88,21 @@ export default function RAGDriftTesting() {
|
||||
) : (
|
||||
<>
|
||||
{ragTests.length > 0 ? (
|
||||
<p>show tests here.</p>
|
||||
<>
|
||||
<RecentTestRuns tests={ragTests} setTests={setRagTests} />
|
||||
<NewTestFormModal
|
||||
organization={organization}
|
||||
postCreate={(test: IRagTest) => {
|
||||
setRagTests([test, ...ragTests]);
|
||||
document.getElementById('new-rag-test-modal')?.close();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<NewTestForm
|
||||
organization={organization}
|
||||
postCreate={(test: object) =>
|
||||
setRagTests([...ragTests, test])
|
||||
postCreate={(test: IRagTest) =>
|
||||
setRagTests([test, ...ragTests])
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -23,7 +23,7 @@ export default function ToolsList({ organization }: { organization: any }) {
|
||||
title="Retrieval Drift Testing & Alerts"
|
||||
description="Catch context drift in production before it reaches your customers as your vector store changes over time."
|
||||
available={true}
|
||||
linkTo={paths.tools.ragDrift(organization)}
|
||||
linkTo={paths.tools.ragTests(organization)}
|
||||
/>
|
||||
<ToolItem
|
||||
title="Migrate vector database to another provider"
|
||||
|
||||
@@ -43,8 +43,14 @@ const paths = {
|
||||
resetTool: function ({ slug }: { slug: string }) {
|
||||
return `/dashboard/${slug}/tools/db-reset`;
|
||||
},
|
||||
ragDrift: function ({ slug }: { slug: string }) {
|
||||
return `/dashboard/${slug}/tools/rag-drift`;
|
||||
ragTests: function ({ slug }: { slug: string }) {
|
||||
return `/dashboard/${slug}/tools/rag-testing`;
|
||||
},
|
||||
ragTestEdit: function (slug: string, testId: number) {
|
||||
return `/dashboard/${slug}/tools/rag-testing/${testId}/edit`;
|
||||
},
|
||||
ragTestRuns: function (slug: string, testId: number) {
|
||||
return `/dashboard/${slug}/tools/rag-testing/${testId}`;
|
||||
},
|
||||
},
|
||||
dashboard: function () {
|
||||
|
||||
Reference in New Issue
Block a user