View tests, delete tests, and see test details

This commit is contained in:
timothycarambat
2023-10-17 19:14:00 -07:00
parent ac26744573
commit 74700e5b38
15 changed files with 749 additions and 253 deletions
+5 -9
View File
@@ -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;
+1 -1
View File
@@ -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
View File
@@ -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
}
}
}
+2 -2
View File
@@ -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,
+3 -3
View File
@@ -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 />} />
+44 -2
View File
@@ -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;
}
@@ -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"
@@ -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>
);
}
@@ -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])
}
/>
)}
+1 -1
View File
@@ -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"
+8 -2
View File
@@ -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 () {