From 4244c0422339b04c2936ca6b60058705b0627656 Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Tue, 3 Sep 2024 21:28:23 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20fix=20`.PDF`=20can=20not?= =?UTF-8?q?=20be=20chunked=20(#3720)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat: support eval page * ✨ feat: 完善数据集页面 * ✨ feat: 完善数据集页面 * ✨ feat: 新增评测任务页面 * ✨ feat: 新增评测任务页面 * 🚧 wip: 新增评测任务功能 * ✨ feat: 完成评测执行流程 * 🐛 fix: 支持 evaluation 的删除和创建 * 支持任务状态展示和数据下载 * fix langchain 分块后缀识别 * fix i18n * fix auth * ♻️ refactor: 重构db * 先添加默认模型 --- locales/ar/knowledgeBase.json | 1 + locales/ar/ragEval.json | 91 + locales/bg-BG/knowledgeBase.json | 1 + locales/bg-BG/ragEval.json | 91 + locales/de-DE/knowledgeBase.json | 1 + locales/de-DE/ragEval.json | 91 + locales/en-US/knowledgeBase.json | 1 + locales/en-US/ragEval.json | 91 + locales/es-ES/knowledgeBase.json | 1 + locales/es-ES/ragEval.json | 91 + locales/fr-FR/knowledgeBase.json | 1 + locales/fr-FR/ragEval.json | 91 + locales/it-IT/knowledgeBase.json | 1 + locales/it-IT/ragEval.json | 91 + locales/ja-JP/knowledgeBase.json | 1 + locales/ja-JP/ragEval.json | 91 + locales/ko-KR/knowledgeBase.json | 1 + locales/ko-KR/ragEval.json | 91 + locales/nl-NL/knowledgeBase.json | 1 + locales/nl-NL/ragEval.json | 91 + locales/pl-PL/knowledgeBase.json | 1 + locales/pl-PL/ragEval.json | 91 + locales/pt-BR/knowledgeBase.json | 1 + locales/pt-BR/ragEval.json | 91 + locales/ru-RU/knowledgeBase.json | 1 + locales/ru-RU/ragEval.json | 91 + locales/tr-TR/knowledgeBase.json | 1 + locales/tr-TR/ragEval.json | 91 + locales/vi-VN/knowledgeBase.json | 1 + locales/vi-VN/ragEval.json | 91 + locales/zh-CN/knowledgeBase.json | 1 + locales/zh-CN/ragEval.json | 91 + locales/zh-TW/knowledgeBase.json | 1 + locales/zh-TW/ragEval.json | 91 + package.json | 1 + .../(main)/repos/[id]/@menu/Head/index.tsx | 17 +- .../(main)/repos/[id]/@menu/Menu/index.tsx | 51 +- src/app/(main)/repos/[id]/@menu/default.tsx | 10 +- .../repos/[id]/evals/components/Container.tsx | 25 + .../repos/[id]/evals/components/Tabs.tsx | 35 + .../dataset/CreateDataset/CreateForm.tsx | 72 + .../evals/dataset/CreateDataset/index.tsx | 37 + .../evals/dataset/DatasetDetail/index.tsx | 126 + .../[id]/evals/dataset/DatasetList/Item.tsx | 59 + .../[id]/evals/dataset/DatasetList/index.tsx | 32 + .../[id]/evals/dataset/EmptyGuide/index.tsx | 33 + .../(main)/repos/[id]/evals/dataset/page.tsx | 47 + .../CreateEvaluation/CreateForm.tsx | 93 + .../evaluation/CreateEvaluation/index.tsx | 28 + .../evaluation/CreateEvaluation/useModal.tsx | 39 + .../evals/evaluation/EmptyGuide/index.tsx | 25 + .../evals/evaluation/EvaluationList/index.tsx | 209 + .../repos/[id]/evals/evaluation/page.tsx | 32 + src/app/(main)/repos/[id]/evals/layout.tsx | 22 + src/app/(main)/repos/[id]/evals/page.tsx | 9 + src/app/(main)/repos/[id]/evals/type.ts | 5 + src/app/(main)/repos/[id]/not-found.tsx | 3 + src/components/FileIcon/index.tsx | 4 +- src/config/featureFlags/schema.ts | 4 +- .../server/migrations/0008_add_rag_evals.sql | 120 + .../server/migrations/meta/0008_snapshot.json | 3463 +++++++++++++++++ .../server/migrations/meta/_journal.json | 7 + src/database/server/models/file.ts | 13 +- src/database/server/models/ragEval/dataset.ts | 59 + .../server/models/ragEval/datasetRecord.ts | 87 + .../server/models/ragEval/evaluation.ts | 96 + .../server/models/ragEval/evaluationRecord.ts | 64 + src/database/server/models/ragEval/index.ts | 4 + .../server/schemas/lobechat/asyncTask.ts | 24 + src/database/server/schemas/lobechat/file.ts | 20 +- src/database/server/schemas/lobechat/index.ts | 2 + .../server/schemas/lobechat/ragEvals.ts | 105 + .../server/schemas/lobechat/relations.ts | 3 +- src/libs/agent-runtime/types/chat.ts | 3 + .../utils/openaiCompatibleFactory/index.ts | 4 +- src/libs/langchain/loaders/index.ts | 2 +- src/locales/default/index.ts | 2 + src/locales/default/knowledgeBase.ts | 1 + src/locales/default/ragEval.ts | 93 + src/server/modules/S3/index.ts | 11 + src/server/routers/async/index.ts | 2 + src/server/routers/async/ragEval.ts | 138 + src/server/routers/lambda/index.ts | 3 +- src/server/routers/lambda/ragEval.ts | 296 ++ src/services/ragEval.ts | 67 + src/services/upload.ts | 15 +- src/store/file/slices/upload/action.ts | 14 +- src/store/knowledgeBase/initialState.ts | 4 +- .../slices/ragEval/actions/dataset.ts | 88 + .../slices/ragEval/actions/evaluation.ts | 62 + .../slices/ragEval/actions/index.ts | 20 + .../knowledgeBase/slices/ragEval/index.ts | 2 + .../slices/ragEval/initialState.ts | 7 + src/store/knowledgeBase/store.ts | 12 +- src/store/serverConfig/selectors.test.ts | 1 + src/types/eval/dataset.ts | 47 + src/types/eval/evaluation.ts | 53 + src/types/eval/index.ts | 3 + src/types/eval/ragas.ts | 9 + 99 files changed, 7531 insertions(+), 77 deletions(-) create mode 100644 locales/ar/ragEval.json create mode 100644 locales/bg-BG/ragEval.json create mode 100644 locales/de-DE/ragEval.json create mode 100644 locales/en-US/ragEval.json create mode 100644 locales/es-ES/ragEval.json create mode 100644 locales/fr-FR/ragEval.json create mode 100644 locales/it-IT/ragEval.json create mode 100644 locales/ja-JP/ragEval.json create mode 100644 locales/ko-KR/ragEval.json create mode 100644 locales/nl-NL/ragEval.json create mode 100644 locales/pl-PL/ragEval.json create mode 100644 locales/pt-BR/ragEval.json create mode 100644 locales/ru-RU/ragEval.json create mode 100644 locales/tr-TR/ragEval.json create mode 100644 locales/vi-VN/ragEval.json create mode 100644 locales/zh-CN/ragEval.json create mode 100644 locales/zh-TW/ragEval.json create mode 100644 src/app/(main)/repos/[id]/evals/components/Container.tsx create mode 100644 src/app/(main)/repos/[id]/evals/components/Tabs.tsx create mode 100644 src/app/(main)/repos/[id]/evals/dataset/CreateDataset/CreateForm.tsx create mode 100644 src/app/(main)/repos/[id]/evals/dataset/CreateDataset/index.tsx create mode 100644 src/app/(main)/repos/[id]/evals/dataset/DatasetDetail/index.tsx create mode 100644 src/app/(main)/repos/[id]/evals/dataset/DatasetList/Item.tsx create mode 100644 src/app/(main)/repos/[id]/evals/dataset/DatasetList/index.tsx create mode 100644 src/app/(main)/repos/[id]/evals/dataset/EmptyGuide/index.tsx create mode 100644 src/app/(main)/repos/[id]/evals/dataset/page.tsx create mode 100644 src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/CreateForm.tsx create mode 100644 src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/index.tsx create mode 100644 src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/useModal.tsx create mode 100644 src/app/(main)/repos/[id]/evals/evaluation/EmptyGuide/index.tsx create mode 100644 src/app/(main)/repos/[id]/evals/evaluation/EvaluationList/index.tsx create mode 100644 src/app/(main)/repos/[id]/evals/evaluation/page.tsx create mode 100644 src/app/(main)/repos/[id]/evals/layout.tsx create mode 100644 src/app/(main)/repos/[id]/evals/page.tsx create mode 100644 src/app/(main)/repos/[id]/evals/type.ts create mode 100644 src/app/(main)/repos/[id]/not-found.tsx create mode 100644 src/database/server/migrations/0008_add_rag_evals.sql create mode 100644 src/database/server/migrations/meta/0008_snapshot.json create mode 100644 src/database/server/models/ragEval/dataset.ts create mode 100644 src/database/server/models/ragEval/datasetRecord.ts create mode 100644 src/database/server/models/ragEval/evaluation.ts create mode 100644 src/database/server/models/ragEval/evaluationRecord.ts create mode 100644 src/database/server/models/ragEval/index.ts create mode 100644 src/database/server/schemas/lobechat/asyncTask.ts create mode 100644 src/database/server/schemas/lobechat/ragEvals.ts create mode 100644 src/locales/default/ragEval.ts create mode 100644 src/server/routers/async/ragEval.ts create mode 100644 src/server/routers/lambda/ragEval.ts create mode 100644 src/services/ragEval.ts create mode 100644 src/store/knowledgeBase/slices/ragEval/actions/dataset.ts create mode 100644 src/store/knowledgeBase/slices/ragEval/actions/evaluation.ts create mode 100644 src/store/knowledgeBase/slices/ragEval/actions/index.ts create mode 100644 src/store/knowledgeBase/slices/ragEval/index.ts create mode 100644 src/store/knowledgeBase/slices/ragEval/initialState.ts create mode 100644 src/types/eval/dataset.ts create mode 100644 src/types/eval/evaluation.ts create mode 100644 src/types/eval/index.ts create mode 100644 src/types/eval/ragas.ts diff --git a/locales/ar/knowledgeBase.json b/locales/ar/knowledgeBase.json index f92343b84da6..708d89460fa9 100644 --- a/locales/ar/knowledgeBase.json +++ b/locales/ar/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "إنشاء قاعدة معرفة جديدة" }, "tab": { + "evals": "تقييمات", "files": "المستندات", "settings": "الإعدادات", "testing": "اختبار الاسترجاع" diff --git a/locales/ar/ragEval.json b/locales/ar/ragEval.json new file mode 100644 index 000000000000..dc28461f9b2b --- /dev/null +++ b/locales/ar/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "إنشاء جديد", + "description": { + "placeholder": "وصف مجموعة البيانات (اختياري)" + }, + "name": { + "placeholder": "اسم مجموعة البيانات", + "required": "يرجى إدخال اسم مجموعة البيانات" + }, + "title": "إضافة مجموعة بيانات" + }, + "dataset": { + "addNewButton": "إنشاء مجموعة بيانات", + "emptyGuide": "مجموعة البيانات الحالية فارغة، يرجى إنشاء مجموعة بيانات.", + "list": { + "table": { + "actions": { + "importData": "استيراد البيانات" + }, + "columns": { + "actions": "الإجراءات", + "ideal": { + "title": "الإجابة المثالية" + }, + "question": { + "title": "السؤال" + }, + "referenceFiles": { + "title": "ملفات مرجعية" + } + }, + "notSelected": "يرجى اختيار مجموعة بيانات من اليسار", + "title": "تفاصيل مجموعة البيانات" + }, + "title": "مجموعة البيانات" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "إنشاء جديد", + "datasetId": { + "placeholder": "يرجى اختيار مجموعة بيانات التقييم الخاصة بك", + "required": "يرجى اختيار مجموعة بيانات التقييم" + }, + "description": { + "placeholder": "وصف مهمة التقييم (اختياري)" + }, + "name": { + "placeholder": "اسم مهمة التقييم", + "required": "يرجى إدخال اسم مهمة التقييم" + }, + "title": "إضافة مهمة تقييم" + }, + "addNewButton": "إنشاء تقييم", + "emptyGuide": "مهمة التقييم الحالية فارغة، ابدأ بإنشاء تقييم.", + "table": { + "columns": { + "actions": { + "checkStatus": "تحقق من الحالة", + "confirmDelete": "هل تريد حذف هذه المهمة من التقييم؟", + "confirmRun": "هل تريد بدء التشغيل؟ بعد بدء التشغيل، سيتم تنفيذ مهمة التقييم في الخلفية بشكل غير متزامن، وإغلاق الصفحة لن يؤثر على تنفيذ المهمة غير المتزامنة.", + "downloadRecords": "تنزيل السجلات", + "retry": "إعادة المحاولة", + "run": "تشغيل", + "title": "الإجراءات" + }, + "datasetId": { + "title": "مجموعة البيانات" + }, + "name": { + "title": "اسم مهمة التقييم" + }, + "records": { + "title": "عدد سجلات التقييم" + }, + "referenceFiles": { + "title": "ملفات مرجعية" + }, + "status": { + "error": "حدث خطأ أثناء التنفيذ", + "pending": "في انتظار التشغيل", + "processing": "جارٍ التشغيل", + "success": "تم التنفيذ بنجاح", + "title": "الحالة" + } + }, + "title": "قائمة مهام التقييم" + } + } +} diff --git a/locales/bg-BG/knowledgeBase.json b/locales/bg-BG/knowledgeBase.json index 943cc5f6cb78..eb667a7648e1 100644 --- a/locales/bg-BG/knowledgeBase.json +++ b/locales/bg-BG/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Създаване на нова знание база" }, "tab": { + "evals": "Оценки", "files": "Документи", "settings": "Настройки", "testing": "Тест за извикване" diff --git a/locales/bg-BG/ragEval.json b/locales/bg-BG/ragEval.json new file mode 100644 index 000000000000..1162af39a90e --- /dev/null +++ b/locales/bg-BG/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Създаване", + "description": { + "placeholder": "Описание на набора от данни (по избор)" + }, + "name": { + "placeholder": "Име на набора от данни", + "required": "Моля, попълнете името на набора от данни" + }, + "title": "Добавяне на набор от данни" + }, + "dataset": { + "addNewButton": "Създаване на набор от данни", + "emptyGuide": "Текущият набор от данни е празен, моля, създайте нов набор от данни.", + "list": { + "table": { + "actions": { + "importData": "Импорт на данни" + }, + "columns": { + "actions": "Действия", + "ideal": { + "title": "Очакван отговор" + }, + "question": { + "title": "Въпрос" + }, + "referenceFiles": { + "title": "Референтни файлове" + } + }, + "notSelected": "Моля, изберете набор от данни отляво", + "title": "Детайли на набора от данни" + }, + "title": "Набор от данни" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Създаване", + "datasetId": { + "placeholder": "Моля, изберете вашия набор от данни за оценка", + "required": "Моля, изберете набор от данни за оценка" + }, + "description": { + "placeholder": "Описание на задачата за оценка (по избор)" + }, + "name": { + "placeholder": "Име на задачата за оценка", + "required": "Моля, попълнете името на задачата за оценка" + }, + "title": "Добавяне на задача за оценка" + }, + "addNewButton": "Създаване на оценка", + "emptyGuide": "Текущата задача за оценка е празна, започнете да създавате оценка.", + "table": { + "columns": { + "actions": { + "checkStatus": "Проверка на статуса", + "confirmDelete": "Наистина ли искате да изтриете тази оценка?", + "confirmRun": "Наистина ли искате да стартирате? След стартиране, задачата за оценка ще се изпълнява асинхронно на заден план, затварянето на страницата няма да повлияе на изпълнението на асинхронната задача.", + "downloadRecords": "Изтегляне на оценки", + "retry": "Опитай отново", + "run": "Стартиране", + "title": "Действия" + }, + "datasetId": { + "title": "Набор от данни" + }, + "name": { + "title": "Име на задачата за оценка" + }, + "records": { + "title": "Брой оценки" + }, + "referenceFiles": { + "title": "Референтни файлове" + }, + "status": { + "error": "Грешка при изпълнение", + "pending": "В очакване на изпълнение", + "processing": "Изпълнява се", + "success": "Успешно изпълнение", + "title": "Статус" + } + }, + "title": "Списък с задачи за оценка" + } + } +} diff --git a/locales/de-DE/knowledgeBase.json b/locales/de-DE/knowledgeBase.json index d7e75a38bd65..a4fa79539bea 100644 --- a/locales/de-DE/knowledgeBase.json +++ b/locales/de-DE/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Wissensdatenbank neu erstellen" }, "tab": { + "evals": "Bewertungen", "files": "Dokumente", "settings": "Einstellungen", "testing": "Rückruf-Test" diff --git a/locales/de-DE/ragEval.json b/locales/de-DE/ragEval.json new file mode 100644 index 000000000000..a4ac459e0b25 --- /dev/null +++ b/locales/de-DE/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Neu erstellen", + "description": { + "placeholder": "Beschreibung des Datensatzes (optional)" + }, + "name": { + "placeholder": "Name des Datensatzes", + "required": "Bitte geben Sie den Namen des Datensatzes ein" + }, + "title": "Datensatz hinzufügen" + }, + "dataset": { + "addNewButton": "Datensatz erstellen", + "emptyGuide": "Der aktuelle Datensatz ist leer, bitte erstellen Sie einen Datensatz.", + "list": { + "table": { + "actions": { + "importData": "Daten importieren" + }, + "columns": { + "actions": "Aktionen", + "ideal": { + "title": "Erwartete Antwort" + }, + "question": { + "title": "Frage" + }, + "referenceFiles": { + "title": "Referenzdateien" + } + }, + "notSelected": "Bitte wählen Sie einen Datensatz auf der linken Seite aus", + "title": "Details zum Datensatz" + }, + "title": "Datensatz" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Neu erstellen", + "datasetId": { + "placeholder": "Bitte wählen Sie Ihren Bewertungsdatensatz aus", + "required": "Bitte wählen Sie einen Bewertungsdatensatz aus" + }, + "description": { + "placeholder": "Beschreibung der Bewertungsaufgabe (optional)" + }, + "name": { + "placeholder": "Name der Bewertungsaufgabe", + "required": "Bitte geben Sie den Namen der Bewertungsaufgabe ein" + }, + "title": "Bewertungsaufgabe hinzufügen" + }, + "addNewButton": "Bewertung erstellen", + "emptyGuide": "Aktuell sind keine Bewertungsaufgaben vorhanden, beginnen Sie mit der Erstellung einer Bewertung.", + "table": { + "columns": { + "actions": { + "checkStatus": "Status überprüfen", + "confirmDelete": "Möchten Sie diese Bewertungsaufgabe wirklich löschen?", + "confirmRun": "Möchten Sie die Ausführung starten? Nach dem Start wird die Bewertungsaufgabe im Hintergrund asynchron ausgeführt, das Schließen der Seite hat keinen Einfluss auf die Ausführung der asynchronen Aufgabe.", + "downloadRecords": "Bewertung herunterladen", + "retry": "Erneut versuchen", + "run": "Ausführen", + "title": "Aktionen" + }, + "datasetId": { + "title": "Datensatz" + }, + "name": { + "title": "Name der Bewertungsaufgabe" + }, + "records": { + "title": "Anzahl der Bewertungsaufzeichnungen" + }, + "referenceFiles": { + "title": "Referenzdateien" + }, + "status": { + "error": "Fehler bei der Ausführung", + "pending": "Warten auf Ausführung", + "processing": "Wird ausgeführt", + "success": "Erfolgreich ausgeführt", + "title": "Status" + } + }, + "title": "Liste der Bewertungsaufgaben" + } + } +} diff --git a/locales/en-US/knowledgeBase.json b/locales/en-US/knowledgeBase.json index 53f8375c6af4..bed43beb7681 100644 --- a/locales/en-US/knowledgeBase.json +++ b/locales/en-US/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Create Knowledge Base" }, "tab": { + "evals": "Evaluations", "files": "Documents", "settings": "Settings", "testing": "Recall Testing" diff --git a/locales/en-US/ragEval.json b/locales/en-US/ragEval.json new file mode 100644 index 000000000000..667522fc799d --- /dev/null +++ b/locales/en-US/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Create", + "description": { + "placeholder": "Dataset description (optional)" + }, + "name": { + "placeholder": "Dataset name", + "required": "Please enter the dataset name" + }, + "title": "Add Dataset" + }, + "dataset": { + "addNewButton": "Create Dataset", + "emptyGuide": "There are currently no datasets. Please create a dataset.", + "list": { + "table": { + "actions": { + "importData": "Import Data" + }, + "columns": { + "actions": "Actions", + "ideal": { + "title": "Expected Answer" + }, + "question": { + "title": "Question" + }, + "referenceFiles": { + "title": "Reference Files" + } + }, + "notSelected": "Please select a dataset on the left", + "title": "Dataset Details" + }, + "title": "Dataset" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Create", + "datasetId": { + "placeholder": "Please select your evaluation dataset", + "required": "Please select an evaluation dataset" + }, + "description": { + "placeholder": "Evaluation task description (optional)" + }, + "name": { + "placeholder": "Evaluation task name", + "required": "Please enter the evaluation task name" + }, + "title": "Add Evaluation Task" + }, + "addNewButton": "Create Evaluation", + "emptyGuide": "There are currently no evaluation tasks. Start creating an evaluation.", + "table": { + "columns": { + "actions": { + "checkStatus": "Check Status", + "confirmDelete": "Are you sure you want to delete this evaluation?", + "confirmRun": "Are you sure you want to start running? The evaluation task will be executed asynchronously in the background, and closing the page will not affect the execution of the asynchronous task.", + "downloadRecords": "Download Evaluation", + "retry": "Retry", + "run": "Run", + "title": "Actions" + }, + "datasetId": { + "title": "Dataset" + }, + "name": { + "title": "Evaluation Task Name" + }, + "records": { + "title": "Number of Evaluation Records" + }, + "referenceFiles": { + "title": "Reference Files" + }, + "status": { + "error": "Execution Error", + "pending": "Pending", + "processing": "In Progress", + "success": "Execution Successful", + "title": "Status" + } + }, + "title": "Evaluation Task List" + } + } +} diff --git a/locales/es-ES/knowledgeBase.json b/locales/es-ES/knowledgeBase.json index a1d7c99b16ed..0d0646f6d697 100644 --- a/locales/es-ES/knowledgeBase.json +++ b/locales/es-ES/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Crear nueva base de conocimiento" }, "tab": { + "evals": "Evaluaciones", "files": "Documentos", "settings": "Configuraciones", "testing": "Prueba de recuperación" diff --git a/locales/es-ES/ragEval.json b/locales/es-ES/ragEval.json new file mode 100644 index 000000000000..71fbdad017af --- /dev/null +++ b/locales/es-ES/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Nuevo", + "description": { + "placeholder": "Descripción del conjunto de datos (opcional)" + }, + "name": { + "placeholder": "Nombre del conjunto de datos", + "required": "Por favor, complete el nombre del conjunto de datos" + }, + "title": "Agregar conjunto de datos" + }, + "dataset": { + "addNewButton": "Crear conjunto de datos", + "emptyGuide": "El conjunto de datos actual está vacío, por favor crea un conjunto de datos.", + "list": { + "table": { + "actions": { + "importData": "Importar datos" + }, + "columns": { + "actions": "Acciones", + "ideal": { + "title": "Respuesta ideal" + }, + "question": { + "title": "Pregunta" + }, + "referenceFiles": { + "title": "Archivos de referencia" + } + }, + "notSelected": "Por favor, selecciona un conjunto de datos a la izquierda", + "title": "Detalles del conjunto de datos" + }, + "title": "Conjunto de datos" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Nuevo", + "datasetId": { + "placeholder": "Por favor, selecciona tu conjunto de datos de evaluación", + "required": "Por favor, selecciona un conjunto de datos de evaluación" + }, + "description": { + "placeholder": "Descripción de la tarea de evaluación (opcional)" + }, + "name": { + "placeholder": "Nombre de la tarea de evaluación", + "required": "Por favor, complete el nombre de la tarea de evaluación" + }, + "title": "Agregar tarea de evaluación" + }, + "addNewButton": "Crear evaluación", + "emptyGuide": "La tarea de evaluación actual está vacía, comienza a crear una evaluación.", + "table": { + "columns": { + "actions": { + "checkStatus": "Verificar estado", + "confirmDelete": "¿Deseas eliminar esta evaluación?", + "confirmRun": "¿Deseas comenzar a ejecutar? Al comenzar, la tarea de evaluación se ejecutará de forma asíncrona en segundo plano, cerrar la página no afectará la ejecución de la tarea asíncrona.", + "downloadRecords": "Descargar evaluación", + "retry": "Reintentar", + "run": "Ejecutar", + "title": "Acciones" + }, + "datasetId": { + "title": "Conjunto de datos" + }, + "name": { + "title": "Nombre de la tarea de evaluación" + }, + "records": { + "title": "Número de registros de evaluación" + }, + "referenceFiles": { + "title": "Archivos de referencia" + }, + "status": { + "error": "Error en la ejecución", + "pending": "Pendiente de ejecución", + "processing": "Ejecutando", + "success": "Ejecución exitosa", + "title": "Estado" + } + }, + "title": "Lista de tareas de evaluación" + } + } +} diff --git a/locales/fr-FR/knowledgeBase.json b/locales/fr-FR/knowledgeBase.json index 83c908e061eb..605b1f35a76b 100644 --- a/locales/fr-FR/knowledgeBase.json +++ b/locales/fr-FR/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Créer une nouvelle base de connaissances" }, "tab": { + "evals": "Évaluations", "files": "Documents", "settings": "Paramètres", "testing": "Test de rappel" diff --git a/locales/fr-FR/ragEval.json b/locales/fr-FR/ragEval.json new file mode 100644 index 000000000000..21c5ba838bd5 --- /dev/null +++ b/locales/fr-FR/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Créer", + "description": { + "placeholder": "Description du jeu de données (optionnel)" + }, + "name": { + "placeholder": "Nom du jeu de données", + "required": "Veuillez remplir le nom du jeu de données" + }, + "title": "Ajouter un jeu de données" + }, + "dataset": { + "addNewButton": "Créer un jeu de données", + "emptyGuide": "Le jeu de données actuel est vide, veuillez en créer un.", + "list": { + "table": { + "actions": { + "importData": "Importer des données" + }, + "columns": { + "actions": "Actions", + "ideal": { + "title": "Réponse idéale" + }, + "question": { + "title": "Question" + }, + "referenceFiles": { + "title": "Fichiers de référence" + } + }, + "notSelected": "Veuillez sélectionner un jeu de données à gauche", + "title": "Détails du jeu de données" + }, + "title": "Jeu de données" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Créer", + "datasetId": { + "placeholder": "Veuillez sélectionner votre jeu de données d'évaluation", + "required": "Veuillez sélectionner un jeu de données d'évaluation" + }, + "description": { + "placeholder": "Description de la tâche d'évaluation (optionnel)" + }, + "name": { + "placeholder": "Nom de la tâche d'évaluation", + "required": "Veuillez remplir le nom de la tâche d'évaluation" + }, + "title": "Ajouter une tâche d'évaluation" + }, + "addNewButton": "Créer une évaluation", + "emptyGuide": "La tâche d'évaluation actuelle est vide, commencez à créer une évaluation.", + "table": { + "columns": { + "actions": { + "checkStatus": "Vérifier l'état", + "confirmDelete": "Voulez-vous supprimer cette évaluation ?", + "confirmRun": "Voulez-vous commencer l'exécution ? L'exécution sera effectuée en arrière-plan de manière asynchrone, fermer la page n'affectera pas l'exécution de la tâche asynchrone.", + "downloadRecords": "Télécharger l'évaluation", + "retry": "Réessayer", + "run": "Exécuter", + "title": "Actions" + }, + "datasetId": { + "title": "Jeu de données" + }, + "name": { + "title": "Nom de la tâche d'évaluation" + }, + "records": { + "title": "Nombre d'enregistrements d'évaluation" + }, + "referenceFiles": { + "title": "Fichiers de référence" + }, + "status": { + "error": "Erreur d'exécution", + "pending": "En attente d'exécution", + "processing": "En cours d'exécution", + "success": "Exécution réussie", + "title": "État" + } + }, + "title": "Liste des tâches d'évaluation" + } + } +} diff --git a/locales/it-IT/knowledgeBase.json b/locales/it-IT/knowledgeBase.json index 2dedcc998435..d80042402d48 100644 --- a/locales/it-IT/knowledgeBase.json +++ b/locales/it-IT/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Crea knowledge base" }, "tab": { + "evals": "Valutazioni", "files": "Documenti", "settings": "Impostazioni", "testing": "Test di richiamo" diff --git a/locales/it-IT/ragEval.json b/locales/it-IT/ragEval.json new file mode 100644 index 000000000000..91284f232b02 --- /dev/null +++ b/locales/it-IT/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Nuovo", + "description": { + "placeholder": "Descrizione del dataset (opzionale)" + }, + "name": { + "placeholder": "Nome del dataset", + "required": "Si prega di inserire il nome del dataset" + }, + "title": "Aggiungi dataset" + }, + "dataset": { + "addNewButton": "Crea dataset", + "emptyGuide": "Il dataset attuale è vuoto, si prega di crearne uno.", + "list": { + "table": { + "actions": { + "importData": "Importa dati" + }, + "columns": { + "actions": "Operazioni", + "ideal": { + "title": "Risposta ideale" + }, + "question": { + "title": "Domanda" + }, + "referenceFiles": { + "title": "File di riferimento" + } + }, + "notSelected": "Si prega di selezionare un dataset a sinistra", + "title": "Dettagli del dataset" + }, + "title": "Dataset" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Nuovo", + "datasetId": { + "placeholder": "Seleziona il tuo dataset di valutazione", + "required": "Si prega di selezionare un dataset di valutazione" + }, + "description": { + "placeholder": "Descrizione del compito di valutazione (opzionale)" + }, + "name": { + "placeholder": "Nome del compito di valutazione", + "required": "Si prega di inserire il nome del compito di valutazione" + }, + "title": "Aggiungi compito di valutazione" + }, + "addNewButton": "Crea valutazione", + "emptyGuide": "Attualmente non ci sono compiti di valutazione, inizia a crearne uno.", + "table": { + "columns": { + "actions": { + "checkStatus": "Controlla stato", + "confirmDelete": "Sei sicuro di voler eliminare questa valutazione?", + "confirmRun": "Sei sicuro di voler avviare l'esecuzione? L'esecuzione avverrà in modo asincrono in background, chiudere la pagina non influenzerà l'esecuzione del compito asincrono.", + "downloadRecords": "Scarica valutazione", + "retry": "Riprova", + "run": "Esegui", + "title": "Operazioni" + }, + "datasetId": { + "title": "Dataset" + }, + "name": { + "title": "Nome del compito di valutazione" + }, + "records": { + "title": "Numero di registrazioni di valutazione" + }, + "referenceFiles": { + "title": "File di riferimento" + }, + "status": { + "error": "Errore durante l'esecuzione", + "pending": "In attesa di esecuzione", + "processing": "In esecuzione", + "success": "Esecuzione riuscita", + "title": "Stato" + } + }, + "title": "Elenco dei compiti di valutazione" + } + } +} diff --git a/locales/ja-JP/knowledgeBase.json b/locales/ja-JP/knowledgeBase.json index ea440fd5fd01..bc61c847e143 100644 --- a/locales/ja-JP/knowledgeBase.json +++ b/locales/ja-JP/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "新しい知識ベースを作成" }, "tab": { + "evals": "評価", "files": "ドキュメント", "settings": "設定", "testing": "リコールテスト" diff --git a/locales/ja-JP/ragEval.json b/locales/ja-JP/ragEval.json new file mode 100644 index 000000000000..2e1af5e22b18 --- /dev/null +++ b/locales/ja-JP/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "新規作成", + "description": { + "placeholder": "データセットの概要(任意)" + }, + "name": { + "placeholder": "データセット名", + "required": "データセット名を入力してください" + }, + "title": "データセットの追加" + }, + "dataset": { + "addNewButton": "データセットを作成", + "emptyGuide": "現在、データセットは空です。データセットを作成してください。", + "list": { + "table": { + "actions": { + "importData": "データをインポート" + }, + "columns": { + "actions": "操作", + "ideal": { + "title": "期待される回答" + }, + "question": { + "title": "質問" + }, + "referenceFiles": { + "title": "参考ファイル" + } + }, + "notSelected": "左側からデータセットを選択してください", + "title": "データセットの詳細" + }, + "title": "データセット" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "新規作成", + "datasetId": { + "placeholder": "評価データセットを選択してください", + "required": "評価データセットを選択してください" + }, + "description": { + "placeholder": "評価タスクの概要(任意)" + }, + "name": { + "placeholder": "評価タスク名", + "required": "評価タスク名を入力してください" + }, + "title": "評価タスクの追加" + }, + "addNewButton": "評価を作成", + "emptyGuide": "現在、評価タスクは空です。評価を作成を開始してください。", + "table": { + "columns": { + "actions": { + "checkStatus": "状態を確認", + "confirmDelete": "この評価を削除しますか?", + "confirmRun": "実行を開始しますか?実行を開始すると、バックグラウンドで非同期に評価タスクが実行されます。ページを閉じても非同期タスクの実行には影響しません。", + "downloadRecords": "評価をダウンロード", + "retry": "再試行", + "run": "実行", + "title": "操作" + }, + "datasetId": { + "title": "データセット" + }, + "name": { + "title": "評価タスク名" + }, + "records": { + "title": "評価記録数" + }, + "referenceFiles": { + "title": "参考ファイル" + }, + "status": { + "error": "実行エラー", + "pending": "実行待ち", + "processing": "実行中", + "success": "実行成功", + "title": "状態" + } + }, + "title": "評価タスク一覧" + } + } +} diff --git a/locales/ko-KR/knowledgeBase.json b/locales/ko-KR/knowledgeBase.json index 2413c8ceb99c..075b956e8e48 100644 --- a/locales/ko-KR/knowledgeBase.json +++ b/locales/ko-KR/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "새 지식베이스 만들기" }, "tab": { + "evals": "평가", "files": "문서", "settings": "설정", "testing": "리콜 테스트" diff --git a/locales/ko-KR/ragEval.json b/locales/ko-KR/ragEval.json new file mode 100644 index 000000000000..5293898a7790 --- /dev/null +++ b/locales/ko-KR/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "새로 만들기", + "description": { + "placeholder": "데이터셋 설명 (선택 사항)" + }, + "name": { + "placeholder": "데이터셋 이름", + "required": "데이터셋 이름을 입력해 주세요" + }, + "title": "데이터셋 추가" + }, + "dataset": { + "addNewButton": "데이터셋 생성", + "emptyGuide": "현재 데이터셋이 비어 있습니다. 데이터셋을 생성해 주세요.", + "list": { + "table": { + "actions": { + "importData": "데이터 가져오기" + }, + "columns": { + "actions": "작업", + "ideal": { + "title": "기대 답변" + }, + "question": { + "title": "질문" + }, + "referenceFiles": { + "title": "참조 파일" + } + }, + "notSelected": "왼쪽에서 데이터셋을 선택해 주세요", + "title": "데이터셋 상세 정보" + }, + "title": "데이터셋" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "새로 만들기", + "datasetId": { + "placeholder": "평가 데이터셋을 선택해 주세요", + "required": "평가 데이터셋을 선택해 주세요" + }, + "description": { + "placeholder": "평가 작업 설명 (선택 사항)" + }, + "name": { + "placeholder": "평가 작업 이름", + "required": "평가 작업 이름을 입력해 주세요" + }, + "title": "평가 작업 추가" + }, + "addNewButton": "평가 생성", + "emptyGuide": "현재 평가 작업이 비어 있습니다. 평가를 시작해 주세요.", + "table": { + "columns": { + "actions": { + "checkStatus": "상태 확인", + "confirmDelete": "이 평가를 삭제하시겠습니까?", + "confirmRun": "실행을 시작하시겠습니까? 실행이 시작되면 백그라운드에서 비동기적으로 평가 작업이 수행됩니다. 페이지를 닫아도 비동기 작업의 실행에는 영향을 미치지 않습니다.", + "downloadRecords": "평가 다운로드", + "retry": "재시도", + "run": "실행", + "title": "작업" + }, + "datasetId": { + "title": "데이터셋" + }, + "name": { + "title": "평가 작업 이름" + }, + "records": { + "title": "평가 기록 수" + }, + "referenceFiles": { + "title": "참조 파일" + }, + "status": { + "error": "실행 오류", + "pending": "대기 중", + "processing": "실행 중", + "success": "실행 성공", + "title": "상태" + } + }, + "title": "평가 작업 목록" + } + } +} diff --git a/locales/nl-NL/knowledgeBase.json b/locales/nl-NL/knowledgeBase.json index 674d1cac63f9..b7958b944a1e 100644 --- a/locales/nl-NL/knowledgeBase.json +++ b/locales/nl-NL/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Nieuwe kennisbank aanmaken" }, "tab": { + "evals": "Beoordelingen", "files": "Documenten", "settings": "Instellingen", "testing": "Herinneringstest" diff --git a/locales/nl-NL/ragEval.json b/locales/nl-NL/ragEval.json new file mode 100644 index 000000000000..e21556bd156b --- /dev/null +++ b/locales/nl-NL/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Nieuw aanmaken", + "description": { + "placeholder": "Beschrijving van de dataset (optioneel)" + }, + "name": { + "placeholder": "Naam van de dataset", + "required": "Vul alstublieft de naam van de dataset in" + }, + "title": "Dataset toevoegen" + }, + "dataset": { + "addNewButton": "Dataset aanmaken", + "emptyGuide": "De huidige dataset is leeg, maak alstublieft een dataset aan.", + "list": { + "table": { + "actions": { + "importData": "Gegevens importeren" + }, + "columns": { + "actions": "Acties", + "ideal": { + "title": "Gewenst antwoord" + }, + "question": { + "title": "Vraag" + }, + "referenceFiles": { + "title": "Referentiebestanden" + } + }, + "notSelected": "Selecteer alstublieft een dataset aan de linkerkant", + "title": "Details van de dataset" + }, + "title": "Dataset" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Nieuw aanmaken", + "datasetId": { + "placeholder": "Selecteer uw evaluatiedataset", + "required": "Selecteer alstublieft een evaluatiedataset" + }, + "description": { + "placeholder": "Beschrijving van de evaluatietaak (optioneel)" + }, + "name": { + "placeholder": "Naam van de evaluatietaak", + "required": "Vul alstublieft de naam van de evaluatietaak in" + }, + "title": "Evaluatietaak toevoegen" + }, + "addNewButton": "Evaluatie aanmaken", + "emptyGuide": "De huidige evaluatietaak is leeg, begin met het aanmaken van een evaluatie.", + "table": { + "columns": { + "actions": { + "checkStatus": "Controleer status", + "confirmDelete": "Weet u zeker dat u deze evaluatie wilt verwijderen?", + "confirmRun": "Weet u zeker dat u wilt starten? Na het starten wordt de evaluatietaak asynchroon op de achtergrond uitgevoerd, het sluiten van de pagina heeft geen invloed op de uitvoering van de asynchrone taak.", + "downloadRecords": "Evaluatie downloaden", + "retry": "Opnieuw proberen", + "run": "Uitvoeren", + "title": "Acties" + }, + "datasetId": { + "title": "Dataset" + }, + "name": { + "title": "Naam van de evaluatietaak" + }, + "records": { + "title": "Aantal evaluatieregisters" + }, + "referenceFiles": { + "title": "Referentiebestanden" + }, + "status": { + "error": "Uitvoering fout", + "pending": "Te uitvoeren", + "processing": "Bezig met uitvoeren", + "success": "Uitvoering succesvol", + "title": "Status" + } + }, + "title": "Lijst van evaluatietaken" + } + } +} diff --git a/locales/pl-PL/knowledgeBase.json b/locales/pl-PL/knowledgeBase.json index 9739a12acae1..d179f3164264 100644 --- a/locales/pl-PL/knowledgeBase.json +++ b/locales/pl-PL/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Utwórz nową bazę wiedzy" }, "tab": { + "evals": "Oceny", "files": "Dokumenty", "settings": "Ustawienia", "testing": "Testowanie przypomnienia" diff --git a/locales/pl-PL/ragEval.json b/locales/pl-PL/ragEval.json new file mode 100644 index 000000000000..f11d0314c74d --- /dev/null +++ b/locales/pl-PL/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Nowy", + "description": { + "placeholder": "Opis zestawu danych (opcjonalnie)" + }, + "name": { + "placeholder": "Nazwa zestawu danych", + "required": "Proszę wpisać nazwę zestawu danych" + }, + "title": "Dodaj zestaw danych" + }, + "dataset": { + "addNewButton": "Utwórz zestaw danych", + "emptyGuide": "Aktualny zestaw danych jest pusty, proszę utworzyć nowy zestaw danych.", + "list": { + "table": { + "actions": { + "importData": "Importuj dane" + }, + "columns": { + "actions": "Operacje", + "ideal": { + "title": "Oczekiwana odpowiedź" + }, + "question": { + "title": "Pytanie" + }, + "referenceFiles": { + "title": "Pliki referencyjne" + } + }, + "notSelected": "Proszę wybrać zestaw danych po lewej stronie", + "title": "Szczegóły zestawu danych" + }, + "title": "Zestaw danych" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Nowy", + "datasetId": { + "placeholder": "Wybierz swój zestaw danych do oceny", + "required": "Proszę wybrać zestaw danych do oceny" + }, + "description": { + "placeholder": "Opis zadania oceny (opcjonalnie)" + }, + "name": { + "placeholder": "Nazwa zadania oceny", + "required": "Proszę wpisać nazwę zadania oceny" + }, + "title": "Dodaj zadanie oceny" + }, + "addNewButton": "Utwórz ocenę", + "emptyGuide": "Aktualne zadania oceny są puste, rozpocznij tworzenie oceny.", + "table": { + "columns": { + "actions": { + "checkStatus": "Sprawdź status", + "confirmDelete": "Czy na pewno chcesz usunąć to zadanie oceny?", + "confirmRun": "Czy chcesz rozpocząć wykonanie? Po rozpoczęciu zadanie oceny będzie wykonywane asynchronicznie w tle, zamknięcie strony nie wpłynie na wykonanie asynchroniczne.", + "downloadRecords": "Pobierz oceny", + "retry": "Spróbuj ponownie", + "run": "Uruchom", + "title": "Operacje" + }, + "datasetId": { + "title": "Zestaw danych" + }, + "name": { + "title": "Nazwa zadania oceny" + }, + "records": { + "title": "Liczba rekordów oceny" + }, + "referenceFiles": { + "title": "Pliki referencyjne" + }, + "status": { + "error": "Wystąpił błąd podczas wykonania", + "pending": "Oczekuje na wykonanie", + "processing": "W trakcie wykonywania", + "success": "Wykonanie zakończone sukcesem", + "title": "Status" + } + }, + "title": "Lista zadań oceny" + } + } +} diff --git a/locales/pt-BR/knowledgeBase.json b/locales/pt-BR/knowledgeBase.json index 444ec1120b69..e770faf28299 100644 --- a/locales/pt-BR/knowledgeBase.json +++ b/locales/pt-BR/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Criar conhecimento" }, "tab": { + "evals": "Avaliações", "files": "Documentos", "settings": "Configurações", "testing": "Teste de recuperação" diff --git a/locales/pt-BR/ragEval.json b/locales/pt-BR/ragEval.json new file mode 100644 index 000000000000..c50b55ebc7a2 --- /dev/null +++ b/locales/pt-BR/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Novo", + "description": { + "placeholder": "Descrição do conjunto de dados (opcional)" + }, + "name": { + "placeholder": "Nome do conjunto de dados", + "required": "Por favor, preencha o nome do conjunto de dados" + }, + "title": "Adicionar Conjunto de Dados" + }, + "dataset": { + "addNewButton": "Criar Conjunto de Dados", + "emptyGuide": "O conjunto de dados atual está vazio, por favor crie um conjunto de dados.", + "list": { + "table": { + "actions": { + "importData": "Importar Dados" + }, + "columns": { + "actions": "Ações", + "ideal": { + "title": "Resposta Esperada" + }, + "question": { + "title": "Pergunta" + }, + "referenceFiles": { + "title": "Arquivos de Referência" + } + }, + "notSelected": "Por favor, selecione um conjunto de dados à esquerda", + "title": "Detalhes do Conjunto de Dados" + }, + "title": "Conjunto de Dados" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Novo", + "datasetId": { + "placeholder": "Por favor, selecione seu conjunto de dados de avaliação", + "required": "Por favor, selecione o conjunto de dados de avaliação" + }, + "description": { + "placeholder": "Descrição da tarefa de avaliação (opcional)" + }, + "name": { + "placeholder": "Nome da tarefa de avaliação", + "required": "Por favor, preencha o nome da tarefa de avaliação" + }, + "title": "Adicionar Tarefa de Avaliação" + }, + "addNewButton": "Criar Avaliação", + "emptyGuide": "A tarefa de avaliação atual está vazia, comece a criar uma avaliação.", + "table": { + "columns": { + "actions": { + "checkStatus": "Verificar Status", + "confirmDelete": "Deseja excluir esta avaliação?", + "confirmRun": "Deseja iniciar a execução? A execução será realizada de forma assíncrona em segundo plano, fechar a página não afetará a execução da tarefa assíncrona.", + "downloadRecords": "Baixar Avaliação", + "retry": "Tentar Novamente", + "run": "Executar", + "title": "Ações" + }, + "datasetId": { + "title": "Conjunto de Dados" + }, + "name": { + "title": "Nome da Tarefa de Avaliação" + }, + "records": { + "title": "Número de Registros de Avaliação" + }, + "referenceFiles": { + "title": "Arquivos de Referência" + }, + "status": { + "error": "Erro na Execução", + "pending": "Aguardando Execução", + "processing": "Executando", + "success": "Execução Bem-Sucedida", + "title": "Status" + } + }, + "title": "Lista de Tarefas de Avaliação" + } + } +} diff --git a/locales/ru-RU/knowledgeBase.json b/locales/ru-RU/knowledgeBase.json index ed0a37710378..0ebf97dcab02 100644 --- a/locales/ru-RU/knowledgeBase.json +++ b/locales/ru-RU/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Создать новую базу знаний" }, "tab": { + "evals": "Оценка", "files": "Документы", "settings": "Настройки", "testing": "Тестирование возврата" diff --git a/locales/ru-RU/ragEval.json b/locales/ru-RU/ragEval.json new file mode 100644 index 000000000000..34dedfc77f85 --- /dev/null +++ b/locales/ru-RU/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Создать", + "description": { + "placeholder": "Описание набора данных (необязательно)" + }, + "name": { + "placeholder": "Название набора данных", + "required": "Пожалуйста, укажите название набора данных" + }, + "title": "Добавить набор данных" + }, + "dataset": { + "addNewButton": "Создать набор данных", + "emptyGuide": "Текущий набор данных пуст, пожалуйста, создайте новый набор данных.", + "list": { + "table": { + "actions": { + "importData": "Импортировать данные" + }, + "columns": { + "actions": "Действия", + "ideal": { + "title": "Ожидаемый ответ" + }, + "question": { + "title": "Вопрос" + }, + "referenceFiles": { + "title": "Справочные файлы" + } + }, + "notSelected": "Пожалуйста, выберите набор данных слева", + "title": "Детали набора данных" + }, + "title": "Набор данных" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Создать", + "datasetId": { + "placeholder": "Пожалуйста, выберите ваш набор данных для оценки", + "required": "Пожалуйста, выберите набор данных для оценки" + }, + "description": { + "placeholder": "Описание задачи оценки (необязательно)" + }, + "name": { + "placeholder": "Название задачи оценки", + "required": "Пожалуйста, укажите название задачи оценки" + }, + "title": "Добавить задачу оценки" + }, + "addNewButton": "Создать оценку", + "emptyGuide": "Текущая задача оценки пуста, начните создание оценки.", + "table": { + "columns": { + "actions": { + "checkStatus": "Проверить статус", + "confirmDelete": "Вы уверены, что хотите удалить эту оценку?", + "confirmRun": "Вы уверены, что хотите запустить? После запуска задача оценки будет выполняться асинхронно в фоновом режиме, закрытие страницы не повлияет на выполнение асинхронной задачи.", + "downloadRecords": "Скачать оценки", + "retry": "Повторить", + "run": "Запустить", + "title": "Действия" + }, + "datasetId": { + "title": "Набор данных" + }, + "name": { + "title": "Название задачи оценки" + }, + "records": { + "title": "Количество записей оценки" + }, + "referenceFiles": { + "title": "Справочные файлы" + }, + "status": { + "error": "Ошибка выполнения", + "pending": "Ожидание выполнения", + "processing": "В процессе выполнения", + "success": "Выполнение успешно", + "title": "Статус" + } + }, + "title": "Список задач оценки" + } + } +} diff --git a/locales/tr-TR/knowledgeBase.json b/locales/tr-TR/knowledgeBase.json index 7f0f6ea4aa8b..c64990187ffa 100644 --- a/locales/tr-TR/knowledgeBase.json +++ b/locales/tr-TR/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Yeni Bilgi Havuzu" }, "tab": { + "evals": "Değerlendirme", "files": "Belgeler", "settings": "Ayarlar", "testing": "Geri Çağırma Testi" diff --git a/locales/tr-TR/ragEval.json b/locales/tr-TR/ragEval.json new file mode 100644 index 000000000000..2f24e0cc4f16 --- /dev/null +++ b/locales/tr-TR/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Yeni Oluştur", + "description": { + "placeholder": "Veri seti açıklaması (isteğe bağlı)" + }, + "name": { + "placeholder": "Veri seti adı", + "required": "Lütfen veri seti adını girin" + }, + "title": "Veri Seti Ekle" + }, + "dataset": { + "addNewButton": "Veri Seti Oluştur", + "emptyGuide": "Mevcut veri seti boş, lütfen bir veri seti oluşturun.", + "list": { + "table": { + "actions": { + "importData": "Veri İçe Aktar" + }, + "columns": { + "actions": "İşlemler", + "ideal": { + "title": "Beklenen Yanıt" + }, + "question": { + "title": "Soru" + }, + "referenceFiles": { + "title": "Referans Dosyaları" + } + }, + "notSelected": "Lütfen soldan bir veri seti seçin", + "title": "Veri Seti Detayları" + }, + "title": "Veri Seti" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Yeni Oluştur", + "datasetId": { + "placeholder": "Lütfen değerlendirme veri setinizi seçin", + "required": "Lütfen değerlendirme veri setini seçin" + }, + "description": { + "placeholder": "Değerlendirme görevi açıklaması (isteğe bağlı)" + }, + "name": { + "placeholder": "Değerlendirme görevi adı", + "required": "Lütfen değerlendirme görevi adını girin" + }, + "title": "Değerlendirme Görevi Ekle" + }, + "addNewButton": "Değerlendirme Oluştur", + "emptyGuide": "Mevcut değerlendirme görevi boş, değerlendirme oluşturmaya başlayın.", + "table": { + "columns": { + "actions": { + "checkStatus": "Durumu Kontrol Et", + "confirmDelete": "Bu değerlendirmeyi silmek istiyor musunuz?", + "confirmRun": "Çalıştırmaya başlamak istiyor musunuz? Çalıştırmaya başladıktan sonra arka planda asenkron olarak değerlendirme görevi yürütülecek, sayfayı kapatmak asenkron görevin yürütülmesini etkilemeyecektir.", + "downloadRecords": "Değerlendirmeyi İndir", + "retry": "Tekrar Dene", + "run": "Çalıştır", + "title": "İşlemler" + }, + "datasetId": { + "title": "Veri Seti" + }, + "name": { + "title": "Değerlendirme Görevi Adı" + }, + "records": { + "title": "Değerlendirme Kayıt Sayısı" + }, + "referenceFiles": { + "title": "Referans Dosyaları" + }, + "status": { + "error": "Yürütme Hatası", + "pending": "Çalıştırmayı Bekliyor", + "processing": "Yürütülüyor", + "success": "Yürütme Başarılı", + "title": "Durum" + } + }, + "title": "Değerlendirme Görevleri Listesi" + } + } +} diff --git a/locales/vi-VN/knowledgeBase.json b/locales/vi-VN/knowledgeBase.json index 3048857c724a..955d9243fee7 100644 --- a/locales/vi-VN/knowledgeBase.json +++ b/locales/vi-VN/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "Tạo kho kiến thức mới" }, "tab": { + "evals": "Đánh giá", "files": "Tài liệu", "settings": "Cài đặt", "testing": "Kiểm tra hồi phục" diff --git a/locales/vi-VN/ragEval.json b/locales/vi-VN/ragEval.json new file mode 100644 index 000000000000..4ef87dc74b11 --- /dev/null +++ b/locales/vi-VN/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "Tạo mới", + "description": { + "placeholder": "Mô tả bộ dữ liệu (tùy chọn)" + }, + "name": { + "placeholder": "Tên bộ dữ liệu", + "required": "Vui lòng điền tên bộ dữ liệu" + }, + "title": "Thêm bộ dữ liệu" + }, + "dataset": { + "addNewButton": "Tạo bộ dữ liệu", + "emptyGuide": "Hiện tại không có bộ dữ liệu nào, vui lòng tạo một bộ dữ liệu.", + "list": { + "table": { + "actions": { + "importData": "Nhập dữ liệu" + }, + "columns": { + "actions": "Hành động", + "ideal": { + "title": "Câu trả lời mong đợi" + }, + "question": { + "title": "Câu hỏi" + }, + "referenceFiles": { + "title": "Tài liệu tham khảo" + } + }, + "notSelected": "Vui lòng chọn bộ dữ liệu ở bên trái", + "title": "Chi tiết bộ dữ liệu" + }, + "title": "Bộ dữ liệu" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "Tạo mới", + "datasetId": { + "placeholder": "Vui lòng chọn bộ dữ liệu đánh giá của bạn", + "required": "Vui lòng chọn bộ dữ liệu đánh giá" + }, + "description": { + "placeholder": "Mô tả nhiệm vụ đánh giá (tùy chọn)" + }, + "name": { + "placeholder": "Tên nhiệm vụ đánh giá", + "required": "Vui lòng điền tên nhiệm vụ đánh giá" + }, + "title": "Thêm nhiệm vụ đánh giá" + }, + "addNewButton": "Tạo đánh giá", + "emptyGuide": "Hiện tại không có nhiệm vụ đánh giá nào, hãy bắt đầu tạo đánh giá.", + "table": { + "columns": { + "actions": { + "checkStatus": "Kiểm tra trạng thái", + "confirmDelete": "Có chắc chắn muốn xóa nhiệm vụ đánh giá này không?", + "confirmRun": "Có chắc chắn muốn bắt đầu chạy không? Sau khi bắt đầu, nhiệm vụ đánh giá sẽ được thực hiện bất đồng bộ ở phía sau, đóng trang không ảnh hưởng đến việc thực hiện nhiệm vụ bất đồng bộ.", + "downloadRecords": "Tải xuống đánh giá", + "retry": "Thử lại", + "run": "Chạy", + "title": "Hành động" + }, + "datasetId": { + "title": "Bộ dữ liệu" + }, + "name": { + "title": "Tên nhiệm vụ đánh giá" + }, + "records": { + "title": "Số lượng bản ghi đánh giá" + }, + "referenceFiles": { + "title": "Tài liệu tham khảo" + }, + "status": { + "error": "Có lỗi trong quá trình thực hiện", + "pending": "Chờ chạy", + "processing": "Đang chạy", + "success": "Thực hiện thành công", + "title": "Trạng thái" + } + }, + "title": "Danh sách nhiệm vụ đánh giá" + } + } +} diff --git a/locales/zh-CN/knowledgeBase.json b/locales/zh-CN/knowledgeBase.json index 2502a59b2393..63a5c45c244a 100644 --- a/locales/zh-CN/knowledgeBase.json +++ b/locales/zh-CN/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "新建知识库" }, "tab": { + "evals": "评测", "files": "文档", "settings": "设置", "testing": "召回测试" diff --git a/locales/zh-CN/ragEval.json b/locales/zh-CN/ragEval.json new file mode 100644 index 000000000000..b6d8b1095890 --- /dev/null +++ b/locales/zh-CN/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "新建", + "description": { + "placeholder": "数据集简介(选填)" + }, + "name": { + "placeholder": "数据集名称", + "required": "请填写数据集名称" + }, + "title": "添加数据集" + }, + "dataset": { + "addNewButton": "创建数据集", + "emptyGuide": "当前数据集为空,请创建一个数据集。", + "list": { + "table": { + "actions": { + "importData": "导入数据" + }, + "columns": { + "actions": "操作", + "ideal": { + "title": "期望回答" + }, + "question": { + "title": "问题" + }, + "referenceFiles": { + "title": "参考文件" + } + }, + "notSelected": "请在左侧选择数据集", + "title": "数据集详情" + }, + "title": "数据集" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "新建", + "datasetId": { + "placeholder": "请选择你的评测数据集", + "required": "请选择评测数据集" + }, + "description": { + "placeholder": "评测任务简介(选填)" + }, + "name": { + "placeholder": "评测任务名称", + "required": "请填写评测任务名称" + }, + "title": "添加评测任务" + }, + "addNewButton": "创建评测", + "emptyGuide": "当前评测任务为空,开始创建评测。", + "table": { + "columns": { + "actions": { + "checkStatus": "检查状态", + "confirmDelete": "是否删除本条评测", + "confirmRun": "是否开始运行?开始运行后将在后台异步执行评测任务,关闭页面不影响异步任务的执行", + "downloadRecords": "下载评测", + "retry": "重试", + "run": "运行", + "title": "操作" + }, + "datasetId": { + "title": "数据集" + }, + "name": { + "title": "评测任务名称" + }, + "records": { + "title": "评测记录数" + }, + "referenceFiles": { + "title": "参考文件" + }, + "status": { + "error": "执行出错", + "pending": "待运行", + "processing": "运行中", + "success": "执行成功", + "title": "状态" + } + }, + "title": "评测任务列表" + } + } +} diff --git a/locales/zh-TW/knowledgeBase.json b/locales/zh-TW/knowledgeBase.json index c1f82471b953..36d48b77b4ed 100644 --- a/locales/zh-TW/knowledgeBase.json +++ b/locales/zh-TW/knowledgeBase.json @@ -23,6 +23,7 @@ "title": "新建知識庫" }, "tab": { + "evals": "評測", "files": "文檔", "settings": "設置", "testing": "召回測試" diff --git a/locales/zh-TW/ragEval.json b/locales/zh-TW/ragEval.json new file mode 100644 index 000000000000..88d0ad6d4fed --- /dev/null +++ b/locales/zh-TW/ragEval.json @@ -0,0 +1,91 @@ +{ + "addDataset": { + "confirm": "新建", + "description": { + "placeholder": "數據集簡介(選填)" + }, + "name": { + "placeholder": "數據集名稱", + "required": "請填寫數據集名稱" + }, + "title": "添加數據集" + }, + "dataset": { + "addNewButton": "創建數據集", + "emptyGuide": "當前數據集為空,請創建一個數據集。", + "list": { + "table": { + "actions": { + "importData": "導入數據" + }, + "columns": { + "actions": "操作", + "ideal": { + "title": "期望回答" + }, + "question": { + "title": "問題" + }, + "referenceFiles": { + "title": "參考文件" + } + }, + "notSelected": "請在左側選擇數據集", + "title": "數據集詳情" + }, + "title": "數據集" + } + }, + "evaluation": { + "addEvaluation": { + "confirm": "新建", + "datasetId": { + "placeholder": "請選擇你的評測數據集", + "required": "請選擇評測數據集" + }, + "description": { + "placeholder": "評測任務簡介(選填)" + }, + "name": { + "placeholder": "評測任務名稱", + "required": "請填寫評測任務名稱" + }, + "title": "添加評測任務" + }, + "addNewButton": "創建評測", + "emptyGuide": "當前評測任務為空,開始創建評測。", + "table": { + "columns": { + "actions": { + "checkStatus": "檢查狀態", + "confirmDelete": "是否刪除本條評測", + "confirmRun": "是否開始運行?開始運行後將在後台異步執行評測任務,關閉頁面不影響異步任務的執行", + "downloadRecords": "下載評測", + "retry": "重試", + "run": "運行", + "title": "操作" + }, + "datasetId": { + "title": "數據集" + }, + "name": { + "title": "評測任務名稱" + }, + "records": { + "title": "評測記錄數" + }, + "referenceFiles": { + "title": "參考文件" + }, + "status": { + "error": "執行出錯", + "pending": "待運行", + "processing": "運行中", + "success": "執行成功", + "title": "狀態" + } + }, + "title": "評測任務列表" + } + } +} diff --git a/package.json b/package.json index b879a6c40898..4e178af81f14 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,7 @@ "ip": "^2.0.1", "jose": "^5.7.0", "js-sha256": "^0.11.0", + "jsonl-parse-stringify": "^1.0.3", "langchain": "^0.2.17", "langfuse": "^3.19.0", "langfuse-core": "^3.19.0", diff --git a/src/app/(main)/repos/[id]/@menu/Head/index.tsx b/src/app/(main)/repos/[id]/@menu/Head/index.tsx index 3e6d8b1c6c81..8d5a06df79c6 100644 --- a/src/app/(main)/repos/[id]/@menu/Head/index.tsx +++ b/src/app/(main)/repos/[id]/@menu/Head/index.tsx @@ -1,17 +1,13 @@ 'use client'; -import { Skeleton, Typography } from 'antd'; +import { Typography } from 'antd'; import { memo } from 'react'; import { Center, Flexbox } from 'react-layout-kit'; import GoBack from '@/components/GoBack'; import RepoIcon from '@/components/RepoIcon'; -import { useKnowledgeBaseItem } from '../../hooks/useKnowledgeItem'; - -const Head = memo<{ id: string }>(({ id }) => { - const { data, isLoading } = useKnowledgeBaseItem(id); - +const Head = memo<{ name?: string }>(({ name }) => { return ( @@ -19,13 +15,8 @@ const Head = memo<{ id: string }>(({ id }) => {
- {isLoading ? ( - - ) : ( - - {data?.name} - - )} + + {name}
); diff --git a/src/app/(main)/repos/[id]/@menu/Menu/index.tsx b/src/app/(main)/repos/[id]/@menu/Menu/index.tsx index b8218a6bc38d..a9ad10b256fa 100644 --- a/src/app/(main)/repos/[id]/@menu/Menu/index.tsx +++ b/src/app/(main)/repos/[id]/@menu/Menu/index.tsx @@ -1,7 +1,7 @@ 'use client'; import { Icon } from '@lobehub/ui'; -import { FileText } from 'lucide-react'; +import { FileText, GaugeIcon } from 'lucide-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { memo, useMemo, useState } from 'react'; @@ -10,31 +10,40 @@ import { Flexbox } from 'react-layout-kit'; import Menu from '@/components/Menu'; import type { MenuProps } from '@/components/Menu'; +import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig'; const FileMenu = memo<{ id: string }>(({ id }) => { const { t } = useTranslation('knowledgeBase'); const pathname = usePathname(); + const { enableRAGEval } = useServerConfigStore(featureFlagsSelectors); + const [activeKey, setActiveKey] = useState( + pathname.startsWith(`/repos/${id}/evals`) ? 'eval' : 'files', + ); - const [activeKey, setActiveKey] = useState(pathname); - - const items: MenuProps['items'] = useMemo( - () => [ - { - icon: , - key: `/repos/${id}`, - label: {t('tab.files')}, - }, - // { - // icon: , - // key: `/repos/${id}/testing`, - // label: {t('tab.testing')}, - // }, - // { - // icon: , - // key: `/repos/${id}/settings`, - // label: {t('tab.settings')}, - // }, - ], + const items = useMemo( + () => + [ + { + icon: , + key: 'files', + label: {t('tab.files')}, + }, + enableRAGEval && { + icon: , + key: 'eval', + label: {t('tab.evals')}, + }, + // { + // icon: , + // key: `/repos/${id}/testing`, + // label: {t('tab.testing')}, + // }, + // { + // icon: , + // key: `/repos/${id}/settings`, + // label: {t('tab.settings')}, + // }, + ].filter(Boolean) as MenuProps['items'], [t], ); diff --git a/src/app/(main)/repos/[id]/@menu/default.tsx b/src/app/(main)/repos/[id]/@menu/default.tsx index 553375133a41..2d25320f15ea 100644 --- a/src/app/(main)/repos/[id]/@menu/default.tsx +++ b/src/app/(main)/repos/[id]/@menu/default.tsx @@ -1,5 +1,8 @@ +import { notFound } from 'next/navigation'; import { Flexbox } from 'react-layout-kit'; +import { KnowledgeBaseModel } from '@/database/server/models/knowledgeBase'; + import Head from './Head'; import Menu from './Menu'; @@ -9,12 +12,15 @@ interface Params { type Props = { params: Params }; -const MenuPage = ({ params }: Props) => { +const MenuPage = async ({ params }: Props) => { const id = params.id; + const item = await KnowledgeBaseModel.findById(params.id); + + if (!item) return notFound(); return ( - + ); diff --git a/src/app/(main)/repos/[id]/evals/components/Container.tsx b/src/app/(main)/repos/[id]/evals/components/Container.tsx new file mode 100644 index 000000000000..49d8ac14b73f --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/components/Container.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { createStyles } from 'antd-style'; +import { PropsWithChildren } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +const useStyles = createStyles(({ css, token }) => ({ + container: css` + padding: 16px; + background: ${token.colorBgContainer}; + border-radius: 8px; + `, +})); + +const Container = ({ children }: PropsWithChildren) => { + const { styles } = useStyles(); + + return ( + + {children} + + ); +}; + +export default Container; diff --git a/src/app/(main)/repos/[id]/evals/components/Tabs.tsx b/src/app/(main)/repos/[id]/evals/components/Tabs.tsx new file mode 100644 index 000000000000..91fed3723a6d --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/components/Tabs.tsx @@ -0,0 +1,35 @@ +'use client'; + +import { TabsNav } from '@lobehub/ui'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; + +export const Tabs = ({ knowledgeBaseId }: { knowledgeBaseId: string }) => { + const pathname = usePathname(); + + const key = pathname.split('/').pop(); + + return ( + + 数据集 + + ), + }, + { + key: 'evaluation', + label: ( + + 评测任务 + + ), + }, + ]} + /> + ); +}; diff --git a/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/CreateForm.tsx b/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/CreateForm.tsx new file mode 100644 index 000000000000..bf4919d5ab3d --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/CreateForm.tsx @@ -0,0 +1,72 @@ +import { Button, Form, Input } from 'antd'; +import { css, cx } from 'antd-style'; +import { memo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { useKnowledgeBaseStore } from '@/store/knowledgeBase'; +import { CreateNewEvalDatasets } from '@/types/eval'; + +const formItem = css` + display: flex; + flex-direction: column; + gap: 12px; + + .ant-form-item { + margin-block-end: 0; + } +`; + +interface CreateFormProps { + knowledgeBaseId: string; + onClose?: () => void; +} + +const CreateForm = memo(({ onClose, knowledgeBaseId }) => { + const { t } = useTranslation('ragEval'); + const [loading, setLoading] = useState(false); + const createNewDataset = useKnowledgeBaseStore((s) => s.createNewDataset); + + const onFinish = async (values: CreateNewEvalDatasets) => { + setLoading(true); + + try { + await createNewDataset({ ...values, knowledgeBaseId }); + setLoading(false); + onClose?.(); + } catch (e) { + console.error(e); + setLoading(false); + } + }; + + return ( + +
+ + + + + + + +
+
+ ); +}); + +export default CreateForm; diff --git a/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/index.tsx b/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/index.tsx new file mode 100644 index 000000000000..4d105788424e --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/index.tsx @@ -0,0 +1,37 @@ +import { Icon } from '@lobehub/ui'; +import { SheetIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { createModal } from '@/components/FunctionModal'; + +import CreateForm from './CreateForm'; + +const Title = () => { + const { t } = useTranslation('ragEval'); + + return ( + + + {t('addDataset.title')} + + ); +}; + +interface CreateDatasetModalProps { + knowledgeBaseId: string; +} + +export const useCreateDatasetModal = createModal((instance, params) => ({ + content: ( + + { + instance.current?.destroy(); + }} + /> + + ), + title: , +})); diff --git a/src/app/(main)/repos/[id]/evals/dataset/DatasetDetail/index.tsx b/src/app/(main)/repos/[id]/evals/dataset/DatasetDetail/index.tsx new file mode 100644 index 000000000000..a478332b4bc9 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/dataset/DatasetDetail/index.tsx @@ -0,0 +1,126 @@ +'use client'; + +import { ProColumns, ProTable } from '@ant-design/pro-components'; +import { ActionIcon } from '@lobehub/ui'; +import { Button, Typography, Upload } from 'antd'; +import { createStyles } from 'antd-style'; +import { Edit2Icon, Trash2Icon } from 'lucide-react'; +import { parseAsInteger, useQueryState } from 'nuqs'; +import { useTranslation } from 'react-i18next'; +import { Center, Flexbox } from 'react-layout-kit'; + +import FileIcon from '@/components/FileIcon'; +import { ragEvalService } from '@/services/ragEval'; +import { useKnowledgeBaseStore } from '@/store/knowledgeBase'; +import { EvalDatasetRecordRefFile } from '@/types/eval'; + +const createRequest = (activeDatasetId: number) => async () => { + const records = await ragEvalService.getDatasetRecords(activeDatasetId); + + return { + data: records, + success: true, + total: records.length, + }; +}; + +const useStyles = createStyles(({ css }) => ({ + container: css` + padding-block: 0; + padding-inline: 12px; + `, + icon: css` + min-width: 24px; + border-radius: 4px; + `, + title: css` + font-size: 16px; + `, +})); + +const DatasetDetail = () => { + const { t } = useTranslation(['ragEval', 'common']); + const { styles } = useStyles(); + const [importDataset] = useKnowledgeBaseStore((s) => [s.importDataset]); + + const [activeDatasetId] = useQueryState('id', parseAsInteger); + + const columns: ProColumns[] = [ + { + dataIndex: 'question', + ellipsis: true, + title: t('dataset.list.table.columns.question.title'), + width: '40%', + }, + { dataIndex: 'ideal', ellipsis: true, title: t('dataset.list.table.columns.ideal.title') }, + { + dataIndex: 'referenceFiles', + render: (dom, entity) => { + const referenceFiles = entity.referenceFiles as EvalDatasetRecordRefFile[]; + + return ( + !!referenceFiles && ( + <Flexbox> + {referenceFiles?.map((file) => ( + <Flexbox gap={4} horizontal key={file.id}> + <FileIcon fileName={file.name} fileType={file.fileType} size={20} /> + <Typography.Text ellipsis={{ tooltip: true }}>{file.name}</Typography.Text> + </Flexbox> + ))} + </Flexbox> + ) + ); + }, + title: t('dataset.list.table.columns.referenceFiles.title'), + width: 200, + }, + { + dataIndex: 'actions', + render: () => ( + <Flexbox gap={4} horizontal> + <ActionIcon icon={Edit2Icon} size={'small'} title={t('edit', { ns: 'common' })} /> + <ActionIcon icon={Trash2Icon} size={'small'} title={t('delete', { ns: 'common' })} /> + </Flexbox> + ), + title: t('dataset.list.table.columns.actions'), + + width: 80, + }, + ]; + + const request = !!activeDatasetId ? createRequest(activeDatasetId) : undefined; + + return !activeDatasetId ? ( + <Center height={'100%'} width={'100%'}> + {t('dataset.list.table.notSelected')} + </Center> + ) : ( + <Flexbox className={styles.container} gap={24}> + <ProTable + columns={columns} + request={request} + search={false} + size={'small'} + toolbar={{ + actions: [ + <Upload + beforeUpload={async (file) => { + await importDataset(file, activeDatasetId); + + return false; + }} + key={'upload'} + multiple={false} + showUploadList={false} + > + <Button type={'primary'}>{t('dataset.list.table.actions.importData')}</Button> + </Upload>, + ], + title: <div className={styles.title}>{t('dataset.list.table.title')}</div>, + }} + /> + </Flexbox> + ); +}; + +export default DatasetDetail; diff --git a/src/app/(main)/repos/[id]/evals/dataset/DatasetList/Item.tsx b/src/app/(main)/repos/[id]/evals/dataset/DatasetList/Item.tsx new file mode 100644 index 000000000000..147c6d6dfde4 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/dataset/DatasetList/Item.tsx @@ -0,0 +1,59 @@ +import { createStyles } from 'antd-style'; +import { parseAsInteger, useQueryState } from 'nuqs'; +import { memo } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +import { RAGEvalDataSetItem } from '@/types/eval'; + +const useStyles = createStyles(({ css, token }) => ({ + active: css` + background: ${token.colorFillTertiary}; + + &:hover { + background-color: ${token.colorFillSecondary}; + } + `, + container: css` + cursor: pointer; + + margin-block-end: 2px; + padding-block: 12px; + padding-inline: 8px; + + border-radius: 8px; + + &:hover { + background-color: ${token.colorFillTertiary}; + } + `, + icon: css` + min-width: 24px; + border-radius: 4px; + `, + title: css` + text-align: start; + `, +})); + +const Item = memo<RAGEvalDataSetItem>(({ name, description, id }) => { + const { styles, cx } = useStyles(); + + const [activeDatasetId, activateDataset] = useQueryState('id', parseAsInteger); + + const isActive = activeDatasetId === id; + return ( + <Flexbox + className={cx(styles.container, isActive && styles.active)} + onClick={() => { + if (!isActive) { + activateDataset(id); + } + }} + > + <div className={styles.title}>{name}</div> + {description && <div>{description}</div>} + </Flexbox> + ); +}); + +export default Item; diff --git a/src/app/(main)/repos/[id]/evals/dataset/DatasetList/index.tsx b/src/app/(main)/repos/[id]/evals/dataset/DatasetList/index.tsx new file mode 100644 index 000000000000..b2888a55e273 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/dataset/DatasetList/index.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { ActionIcon } from '@lobehub/ui'; +import { PlusIcon } from 'lucide-react'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; +import { Virtuoso } from 'react-virtuoso'; + +import { RAGEvalDataSetItem } from '@/types/eval'; + +import Item from './Item'; + +interface DatasetListProps { + dataSource: RAGEvalDataSetItem[]; +} + +const DatasetList = memo<DatasetListProps>(({ dataSource }) => { + const { t } = useTranslation('ragEval'); + + return ( + <Flexbox gap={24} height={'100%'}> + <Flexbox align={'center'} horizontal justify={'space-between'}> + <span>{t('dataset.list.title')}</span> + <ActionIcon icon={PlusIcon} size={'small'} /> + </Flexbox> + <Virtuoso data={dataSource} itemContent={(index, data) => <Item {...data} key={data.id} />} /> + </Flexbox> + ); +}); + +export default DatasetList; diff --git a/src/app/(main)/repos/[id]/evals/dataset/EmptyGuide/index.tsx b/src/app/(main)/repos/[id]/evals/dataset/EmptyGuide/index.tsx new file mode 100644 index 000000000000..ecab8749d727 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/dataset/EmptyGuide/index.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { Button } from 'antd'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Center, Flexbox } from 'react-layout-kit'; + +import { useCreateDatasetModal } from '../CreateDataset'; + +interface EmptyGuideProps { + knowledgeBaseId: string; +} + +const EmptyGuide = memo<EmptyGuideProps>(({ knowledgeBaseId }) => { + const { t } = useTranslation('ragEval'); + const modal = useCreateDatasetModal(); + return ( + <Center gap={24} height={'100%'} width={'100%'}> + <div>{t('dataset.emptyGuide')}</div> + <Flexbox gap={8} horizontal> + <Button + onClick={() => { + modal.open({ knowledgeBaseId }); + }} + type={'primary'} + > + {t('dataset.addNewButton')} + </Button> + </Flexbox> + </Center> + ); +}); +export default EmptyGuide; diff --git a/src/app/(main)/repos/[id]/evals/dataset/page.tsx b/src/app/(main)/repos/[id]/evals/dataset/page.tsx new file mode 100644 index 000000000000..34221f886659 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/dataset/page.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { createStyles } from 'antd-style'; +import { Flexbox } from 'react-layout-kit'; + +import CircleLoading from '@/components/CircleLoading'; +import { useKnowledgeBaseStore } from '@/store/knowledgeBase'; + +import { PageProps } from '../type'; +import DatasetDetail from './DatasetDetail'; +import DatasetList from './DatasetList'; +import EmptyGuide from './EmptyGuide'; + +const useStyles = createStyles(({ css, token }) => ({ + sider: css` + padding-inline-end: 12px; + border-inline-end: 1px solid ${token.colorSplit}; + `, +})); + +const Dataset = ({ params }: PageProps) => { + const { styles } = useStyles(); + const knowledgeBaseId = params.id; + + const useFetchDatasets = useKnowledgeBaseStore((s) => s.useFetchDatasets); + + const { data, isLoading } = useFetchDatasets(knowledgeBaseId); + + const isEmpty = data?.length === 0; + + return isLoading ? ( + <CircleLoading /> + ) : isEmpty ? ( + <EmptyGuide knowledgeBaseId={knowledgeBaseId} /> + ) : ( + <Flexbox height={'100%'} horizontal> + <Flexbox className={styles.sider} width={200}> + <DatasetList dataSource={data!} /> + </Flexbox> + <Flexbox width={'100%'}> + <DatasetDetail /> + </Flexbox> + </Flexbox> + ); +}; + +export default Dataset; diff --git a/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/CreateForm.tsx b/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/CreateForm.tsx new file mode 100644 index 000000000000..a163debf314d --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/CreateForm.tsx @@ -0,0 +1,93 @@ +import { Button, Form, Input, Select } from 'antd'; +import { css, cx } from 'antd-style'; +import { memo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { useKnowledgeBaseStore } from '@/store/knowledgeBase'; +import { CreateNewEvalEvaluation } from '@/types/eval'; + +const formItem = css` + display: flex; + flex-direction: column; + gap: 12px; + + .ant-form-item { + margin-block-end: 0; + } +`; + +interface CreateFormProps { + knowledgeBaseId: string; + onClose?: () => void; + onCreate?: () => void; +} + +const CreateForm = memo<CreateFormProps>(({ onClose, onCreate, knowledgeBaseId }) => { + const { t } = useTranslation('ragEval'); + + const [loading, setLoading] = useState(false); + const [useFetchDatasets, createNewEvaluation] = useKnowledgeBaseStore((s) => [ + s.useFetchDatasets, + s.createNewEvaluation, + ]); + + const { data, isLoading } = useFetchDatasets(knowledgeBaseId); + + const onFinish = async (values: CreateNewEvalEvaluation) => { + setLoading(true); + + try { + await createNewEvaluation({ ...values, knowledgeBaseId }); + setLoading(false); + onCreate?.(); + onClose?.(); + } catch (e) { + console.error(e); + setLoading(false); + } + }; + + return ( + <Flexbox gap={8}> + <Form className={cx(formItem)} onFinish={onFinish}> + <Form.Item + name={'name'} + rules={[{ message: t('evaluation.addEvaluation.name.required'), required: true }]} + > + <Input autoFocus placeholder={t('evaluation.addEvaluation.name.placeholder')} /> + </Form.Item> + <Form.Item name={'description'}> + <Input.TextArea + placeholder={t('evaluation.addEvaluation.description.placeholder')} + style={{ minHeight: 120 }} + /> + </Form.Item> + <Form.Item + name={'datasetId'} + rules={[{ message: t('evaluation.addEvaluation.datasetId.required'), required: true }]} + > + <Select + loading={isLoading} + options={data?.map((item) => ({ + label: item.name, + value: item.id, + }))} + placeholder={t('evaluation.addEvaluation.datasetId.placeholder')} + /> + </Form.Item> + <Button + block + htmlType={'submit'} + loading={loading} + style={{ marginTop: 16 }} + type={'primary'} + > + {t('evaluation.addEvaluation.confirm')} + </Button> + </Form> + </Flexbox> + ); +}); + +export default CreateForm; diff --git a/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/index.tsx b/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/index.tsx new file mode 100644 index 000000000000..752809a5d938 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/index.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { Button } from 'antd'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useCreateDatasetModal } from '../CreateEvaluation/useModal'; + +interface CreateEvaluationProps { + knowledgeBaseId: string; + onCreate?: () => void; +} + +const CreateEvaluation = memo<CreateEvaluationProps>(({ knowledgeBaseId, onCreate }) => { + const { t } = useTranslation('ragEval'); + const modal = useCreateDatasetModal(); + return ( + <Button + onClick={() => { + modal.open({ knowledgeBaseId, onCreate }); + }} + type={'primary'} + > + {t('evaluation.addNewButton')} + </Button> + ); +}); +export default CreateEvaluation; diff --git a/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/useModal.tsx b/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/useModal.tsx new file mode 100644 index 000000000000..d6fcf66c16fb --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/useModal.tsx @@ -0,0 +1,39 @@ +import { Icon } from '@lobehub/ui'; +import { SheetIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { createModal } from '@/components/FunctionModal'; + +import CreateForm from './CreateForm'; + +const Title = () => { + const { t } = useTranslation('ragEval'); + + return ( + <Flexbox gap={8} horizontal> + <Icon icon={SheetIcon} /> + {t('addDataset.title')} + </Flexbox> + ); +}; + +interface CreateDatasetModalProps { + knowledgeBaseId: string; + onCreate?: () => void; +} + +export const useCreateDatasetModal = createModal<CreateDatasetModalProps>((instance, params) => ({ + content: ( + <Flexbox paddingInline={16} style={{ marginBlock: 24 }}> + <CreateForm + knowledgeBaseId={params!.knowledgeBaseId} + onClose={() => { + instance.current?.destroy(); + }} + onCreate={params?.onCreate} + /> + </Flexbox> + ), + title: <Title />, +})); diff --git a/src/app/(main)/repos/[id]/evals/evaluation/EmptyGuide/index.tsx b/src/app/(main)/repos/[id]/evals/evaluation/EmptyGuide/index.tsx new file mode 100644 index 000000000000..35f3cf2d5441 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/evaluation/EmptyGuide/index.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Center, Flexbox } from 'react-layout-kit'; + +import CreateEvaluationButton from '../CreateEvaluation'; + +interface EmptyGuideProps { + knowledgeBaseId: string; +} + +const EmptyGuide = memo<EmptyGuideProps>(({ knowledgeBaseId }) => { + const { t } = useTranslation('ragEval'); + + return ( + <Center gap={24} height={'100%'} width={'100%'}> + <div>{t('evaluation.emptyGuide')}</div> + <Flexbox gap={8} horizontal> + <CreateEvaluationButton knowledgeBaseId={knowledgeBaseId} /> + </Flexbox> + </Center> + ); +}); +export default EmptyGuide; diff --git a/src/app/(main)/repos/[id]/evals/evaluation/EvaluationList/index.tsx b/src/app/(main)/repos/[id]/evals/evaluation/EvaluationList/index.tsx new file mode 100644 index 000000000000..f645c25f72e0 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/evaluation/EvaluationList/index.tsx @@ -0,0 +1,209 @@ +'use client'; + +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; +import { ActionIcon, Icon } from '@lobehub/ui'; +import { App, Button, ButtonProps, Typography } from 'antd'; +import { createStyles } from 'antd-style'; +import { DownloadIcon, PlayIcon, RotateCcwIcon, Trash2Icon } from 'lucide-react'; +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { ragEvalService } from '@/services/ragEval'; +import { useKnowledgeBaseStore } from '@/store/knowledgeBase'; +import { EvalEvaluationStatus, RAGEvalEvaluationItem } from '@/types/eval'; + +import CreateEvaluationButton from '../CreateEvaluation'; + +const createRequest = (knowledgeBaseId: string) => async () => { + const records = await ragEvalService.getEvaluationList(knowledgeBaseId); + + return { + data: records, + success: true, + total: records.length, + }; +}; + +const useStyles = createStyles(({ css }) => ({ + icon: css` + min-width: 24px; + border-radius: 4px; + `, + title: css` + font-size: 16px; + `, +})); + +const EvaluationList = ({ knowledgeBaseId }: { knowledgeBaseId: string }) => { + const { t } = useTranslation(['ragEval', 'common']); + const { styles } = useStyles(); + const [removeEvaluation, runEvaluation, checkEvaluationStatus] = useKnowledgeBaseStore((s) => [ + s.removeEvaluation, + s.runEvaluation, + s.checkEvaluationStatus, + ]); + const [isCheckingStatus, setCheckingStatus] = useState(false); + const { modal } = App.useApp(); + const actionRef = useRef<ActionType>(); + + const columns: ProColumns<RAGEvalEvaluationItem>[] = [ + { + dataIndex: 'name', + ellipsis: true, + title: t('evaluation.table.columns.name.title'), + }, + { + dataIndex: ['dataset', 'id'], + render: (dom, entity) => { + return ( + <Typography.Link + href={`/repos/${knowledgeBaseId}/evals/dataset?id=${entity.dataset.id}`} + style={{ color: 'initial' }} + target={'_blank'} + > + {entity.dataset.name} + </Typography.Link> + ); + }, + title: t('evaluation.table.columns.datasetId.title'), + width: 200, + }, + { + dataIndex: 'status', + title: t('evaluation.table.columns.status.title'), + valueEnum: { + [EvalEvaluationStatus.Error]: { + status: 'error', + text: t('evaluation.table.columns.status.error'), + }, + [EvalEvaluationStatus.Processing]: { + status: 'processing', + text: t('evaluation.table.columns.status.processing'), + }, + [EvalEvaluationStatus.Pending]: { + status: 'default', + text: t('evaluation.table.columns.status.pending'), + }, + [EvalEvaluationStatus.Success]: { + status: 'success', + text: t('evaluation.table.columns.status.success'), + }, + }, + }, + { + dataIndex: ['recordsStats', 'total'], + render: (dom, entity) => { + return entity.status === 'Pending' + ? entity.recordsStats.total + : `${entity.recordsStats.success}/${entity.recordsStats.total}`; + }, + title: t('evaluation.table.columns.records.title'), + }, + { + dataIndex: 'actions', + render: (_, entity) => { + const actionsMap: Record<EvalEvaluationStatus, ButtonProps> = { + [EvalEvaluationStatus.Pending]: { + children: t('evaluation.table.columns.actions.run'), + icon: <Icon icon={PlayIcon} />, + onClick: () => { + modal.confirm({ + content: t('evaluation.table.columns.actions.confirmRun'), + onOk: async () => { + await runEvaluation(entity.id); + await actionRef.current?.reload(); + }, + }); + }, + }, + [EvalEvaluationStatus.Error]: { + children: t('evaluation.table.columns.actions.retry'), + icon: <Icon icon={RotateCcwIcon} />, + onClick: () => { + modal.confirm({ + content: t('evaluation.table.columns.actions.confirmRun'), + onOk: async () => { + await runEvaluation(entity.id); + await actionRef.current?.reload(); + }, + }); + }, + }, + [EvalEvaluationStatus.Processing]: { + children: t('evaluation.table.columns.actions.checkStatus'), + icon: null, + loading: isCheckingStatus, + onClick: async () => { + setCheckingStatus(true); + await checkEvaluationStatus(entity.id); + setCheckingStatus(false); + await actionRef.current?.reload(); + }, + }, + [EvalEvaluationStatus.Success]: { + children: t('evaluation.table.columns.actions.downloadRecords'), + icon: <Icon icon={DownloadIcon} />, + onClick: async () => { + window.open(entity.evalRecordsUrl); + }, + }, + }; + + const actionProps = actionsMap[entity.status]; + + return ( + <Flexbox gap={4} horizontal> + {!actionProps ? null : <Button {...actionProps} size={'small'} />} + <ActionIcon + icon={Trash2Icon} + onClick={async () => { + modal.confirm({ + content: t('evaluation.table.columns.actions.confirmDelete'), + okButtonProps: { + danger: true, + }, + onOk: async () => { + await removeEvaluation(entity.id); + await actionRef.current?.reload(); + }, + }); + }} + size={'small'} + title={t('delete', { ns: 'common' })} + /> + </Flexbox> + ); + }, + title: t('evaluation.table.columns.actions.title'), + width: 120, + }, + ]; + + const request = knowledgeBaseId ? createRequest(knowledgeBaseId) : undefined; + + return ( + <Flexbox gap={24}> + <ProTable + actionRef={actionRef} + columns={columns} + request={request} + search={false} + toolbar={{ + actions: [ + <CreateEvaluationButton + key={'new'} + knowledgeBaseId={knowledgeBaseId} + onCreate={() => { + actionRef.current?.reload(); + }} + />, + ], + title: <div className={styles.title}>{t('evaluation.table.title')}</div>, + }} + /> + </Flexbox> + ); +}; + +export default EvaluationList; diff --git a/src/app/(main)/repos/[id]/evals/evaluation/page.tsx b/src/app/(main)/repos/[id]/evals/evaluation/page.tsx new file mode 100644 index 000000000000..5e90d79aaf4c --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/evaluation/page.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { Flexbox } from 'react-layout-kit'; + +import CircleLoading from '@/components/CircleLoading'; +import { useKnowledgeBaseStore } from '@/store/knowledgeBase'; + +import { PageProps } from '../type'; +import EmptyGuide from './EmptyGuide'; +import EvaluationList from './EvaluationList'; + +const Evaluation = ({ params }: PageProps) => { + const knowledgeBaseId = params.id; + + const useFetchEvaluation = useKnowledgeBaseStore((s) => s.useFetchEvaluationList); + + const { data, isLoading } = useFetchEvaluation(knowledgeBaseId); + + const isEmpty = data?.length === 0; + + return isLoading ? ( + <CircleLoading /> + ) : isEmpty ? ( + <EmptyGuide knowledgeBaseId={knowledgeBaseId} /> + ) : ( + <Flexbox height={'100%'}> + <EvaluationList knowledgeBaseId={knowledgeBaseId} /> + </Flexbox> + ); +}; + +export default Evaluation; diff --git a/src/app/(main)/repos/[id]/evals/layout.tsx b/src/app/(main)/repos/[id]/evals/layout.tsx new file mode 100644 index 000000000000..d90c2af0c270 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/layout.tsx @@ -0,0 +1,22 @@ +import { notFound } from 'next/navigation'; +import { PropsWithChildren } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +import { serverFeatureFlags } from '@/config/featureFlags'; + +import Container from './components/Container'; +import { Tabs } from './components/Tabs'; +import { PageProps } from './type'; + +export default ({ children, params }: PropsWithChildren<PageProps>) => { + const enableRAGEval = serverFeatureFlags().enableRAGEval; + + if (!enableRAGEval) return notFound(); + + return ( + <Flexbox gap={24} height={'100%'} padding={24} style={{ paddingTop: 0 }}> + <Tabs knowledgeBaseId={params.id} /> + <Container>{children}</Container> + </Flexbox> + ); +}; diff --git a/src/app/(main)/repos/[id]/evals/page.tsx b/src/app/(main)/repos/[id]/evals/page.tsx new file mode 100644 index 000000000000..0ad71aabe2b2 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/page.tsx @@ -0,0 +1,9 @@ +import { redirect } from 'next/navigation'; + +interface Params { + id: string; +} + +type Props = { params: Params }; + +export default ({ params }: Props) => redirect(`/repos/${params.id}/evals/dataset`); diff --git a/src/app/(main)/repos/[id]/evals/type.ts b/src/app/(main)/repos/[id]/evals/type.ts new file mode 100644 index 000000000000..05fd931faba7 --- /dev/null +++ b/src/app/(main)/repos/[id]/evals/type.ts @@ -0,0 +1,5 @@ +interface Params { + id: string; +} + +export type PageProps = { params: Params }; diff --git a/src/app/(main)/repos/[id]/not-found.tsx b/src/app/(main)/repos/[id]/not-found.tsx new file mode 100644 index 000000000000..02503bc7fa46 --- /dev/null +++ b/src/app/(main)/repos/[id]/not-found.tsx @@ -0,0 +1,3 @@ +import dynamic from 'next/dynamic'; + +export default dynamic(() => import('@/components/404')); diff --git a/src/components/FileIcon/index.tsx b/src/components/FileIcon/index.tsx index e9d03c1dcf6c..8239d4fdaec7 100644 --- a/src/components/FileIcon/index.tsx +++ b/src/components/FileIcon/index.tsx @@ -11,8 +11,8 @@ interface FileListProps { } const FileIcon = memo<FileListProps>(({ fileName, size, variant = 'file' }) => { - if (Object.keys(mimeTypeMap).some((key) => fileName.endsWith(`.${key}`))) { - const ext = fileName.split('.').pop() as string; + if (Object.keys(mimeTypeMap).some((key) => fileName?.toLowerCase().endsWith(`.${key}`))) { + const ext = fileName.split('.').pop()?.toLowerCase() as string; return ( <FileTypeIcon diff --git a/src/config/featureFlags/schema.ts b/src/config/featureFlags/schema.ts index 1b1d4a9dddeb..1b73febb61fa 100644 --- a/src/config/featureFlags/schema.ts +++ b/src/config/featureFlags/schema.ts @@ -25,9 +25,9 @@ export const FeatureFlagsSchema = z.object({ speech_to_text: z.boolean().optional(), knowledge_base: z.boolean().optional(), + rag_eval: z.boolean().optional(), }); -// TypeScript 类型,从 Zod schema 生成 export type IFeatureFlags = z.infer<typeof FeatureFlagsSchema>; export const DEFAULT_FEATURE_FLAGS: IFeatureFlags = { @@ -47,6 +47,7 @@ export const DEFAULT_FEATURE_FLAGS: IFeatureFlags = { welcome_suggest: true, knowledge_base: true, + rag_eval: false, clerk_sign_up: true, @@ -75,6 +76,7 @@ export const mapFeatureFlagsEnvToState = (config: IFeatureFlags) => { enableClerkSignUp: config.clerk_sign_up, enableKnowledgeBase: config.knowledge_base, + enableRAGEval: config.rag_eval, showCloudPromotion: config.cloud_promotion, diff --git a/src/database/server/migrations/0008_add_rag_evals.sql b/src/database/server/migrations/0008_add_rag_evals.sql new file mode 100644 index 000000000000..5cb26e4b40ec --- /dev/null +++ b/src/database/server/migrations/0008_add_rag_evals.sql @@ -0,0 +1,120 @@ +CREATE TABLE IF NOT EXISTS "rag_eval_dataset_records" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "rag_eval_dataset_records_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "dataset_id" integer NOT NULL, + "ideal" text, + "question" text, + "reference_files" text[], + "metadata" jsonb, + "user_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "rag_eval_datasets" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "rag_eval_datasets_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 30000 CACHE 1), + "description" text, + "name" text NOT NULL, + "knowledge_base_id" text, + "user_id" text, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "rag_eval_evaluations" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "rag_eval_evaluations_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "name" text NOT NULL, + "description" text, + "eval_records_url" text, + "status" text, + "error" jsonb, + "dataset_id" integer NOT NULL, + "knowledge_base_id" text, + "language_model" text, + "embedding_model" text, + "user_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "rag_eval_evaluation_records" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "rag_eval_evaluation_records_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "question" text NOT NULL, + "answer" text, + "context" text[], + "ideal" text, + "status" text, + "error" jsonb, + "language_model" text, + "embedding_model" text, + "question_embedding_id" uuid, + "duration" integer, + "dataset_record_id" integer NOT NULL, + "evaluation_id" integer NOT NULL, + "user_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_dataset_records" ADD CONSTRAINT "rag_eval_dataset_records_dataset_id_rag_eval_datasets_id_fk" FOREIGN KEY ("dataset_id") REFERENCES "public"."rag_eval_datasets"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_dataset_records" ADD CONSTRAINT "rag_eval_dataset_records_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_datasets" ADD CONSTRAINT "rag_eval_datasets_knowledge_base_id_knowledge_bases_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "public"."knowledge_bases"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_datasets" ADD CONSTRAINT "rag_eval_datasets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_evaluations" ADD CONSTRAINT "rag_eval_evaluations_dataset_id_rag_eval_datasets_id_fk" FOREIGN KEY ("dataset_id") REFERENCES "public"."rag_eval_datasets"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_evaluations" ADD CONSTRAINT "rag_eval_evaluations_knowledge_base_id_knowledge_bases_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "public"."knowledge_bases"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_evaluations" ADD CONSTRAINT "rag_eval_evaluations_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_evaluation_records" ADD CONSTRAINT "rag_eval_evaluation_records_question_embedding_id_embeddings_id_fk" FOREIGN KEY ("question_embedding_id") REFERENCES "public"."embeddings"("id") ON DELETE set null ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_evaluation_records" ADD CONSTRAINT "rag_eval_evaluation_records_dataset_record_id_rag_eval_dataset_records_id_fk" FOREIGN KEY ("dataset_record_id") REFERENCES "public"."rag_eval_dataset_records"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_evaluation_records" ADD CONSTRAINT "rag_eval_evaluation_records_evaluation_id_rag_eval_evaluations_id_fk" FOREIGN KEY ("evaluation_id") REFERENCES "public"."rag_eval_evaluations"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "rag_eval_evaluation_records" ADD CONSTRAINT "rag_eval_evaluation_records_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/src/database/server/migrations/meta/0008_snapshot.json b/src/database/server/migrations/meta/0008_snapshot.json new file mode 100644 index 000000000000..7aa6e17e21ef --- /dev/null +++ b/src/database/server/migrations/meta/0008_snapshot.json @@ -0,0 +1,3463 @@ +{ + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + }, + "dialect": "postgresql", + "enums": {}, + "id": "a28836b8-99dc-4a31-97be-a6e29d0c74d1", + "prevId": "5b855702-5dff-4ae1-bd09-a0453c153aaa", + "schemas": {}, + "sequences": {}, + "tables": { + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_color": { + "name": "background_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plugins": { + "name": "plugins", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_config": { + "name": "chat_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "few_shots": { + "name": "few_shots", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_role": { + "name": "system_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tts": { + "name": "tts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_user_id_users_id_fk": { + "name": "agents_user_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_slug_unique": { + "name": "agents_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + } + }, + "public.agents_files": { + "name": "agents_files", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_files_file_id_files_id_fk": { + "name": "agents_files_file_id_files_id_fk", + "tableFrom": "agents_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_files_agent_id_agents_id_fk": { + "name": "agents_files_agent_id_agents_id_fk", + "tableFrom": "agents_files", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_files_user_id_users_id_fk": { + "name": "agents_files_user_id_users_id_fk", + "tableFrom": "agents_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_files_file_id_agent_id_user_id_pk": { + "name": "agents_files_file_id_agent_id_user_id_pk", + "columns": ["file_id", "agent_id", "user_id"] + } + }, + "uniqueConstraints": {} + }, + "public.agents_knowledge_bases": { + "name": "agents_knowledge_bases", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_knowledge_bases_agent_id_agents_id_fk": { + "name": "agents_knowledge_bases_agent_id_agents_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_knowledge_bases_knowledge_base_id_knowledge_bases_id_fk": { + "name": "agents_knowledge_bases_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_knowledge_bases_user_id_users_id_fk": { + "name": "agents_knowledge_bases_user_id_users_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_knowledge_bases_agent_id_knowledge_base_id_pk": { + "name": "agents_knowledge_bases_agent_id_knowledge_base_id_pk", + "columns": ["agent_id", "knowledge_base_id"] + } + }, + "uniqueConstraints": {} + }, + "public.async_tasks": { + "name": "async_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "async_tasks_user_id_users_id_fk": { + "name": "async_tasks_user_id_users_id_fk", + "tableFrom": "async_tasks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.agents_tags": { + "name": "agents_tags", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "agents_tags_agent_id_agents_id_fk": { + "name": "agents_tags_agent_id_agents_id_fk", + "tableFrom": "agents_tags", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_tags_tag_id_tags_id_fk": { + "name": "agents_tags_tag_id_tags_id_fk", + "tableFrom": "agents_tags", + "tableTo": "tags", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_tags_agent_id_tag_id_pk": { + "name": "agents_tags_agent_id_tag_id_pk", + "columns": ["agent_id", "tag_id"] + } + }, + "uniqueConstraints": {} + }, + "public.market": { + "name": "market", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "view": { + "name": "view", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "like": { + "name": "like", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "market_agent_id_agents_id_fk": { + "name": "market_agent_id_agents_id_fk", + "tableFrom": "market", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "market_plugin_id_plugins_id_fk": { + "name": "market_plugin_id_plugins_id_fk", + "tableFrom": "market", + "tableTo": "plugins", + "columnsFrom": ["plugin_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "market_user_id_users_id_fk": { + "name": "market_user_id_users_id_fk", + "tableFrom": "market", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugins_identifier_unique": { + "name": "plugins_identifier_unique", + "nullsNotDistinct": false, + "columns": ["identifier"] + } + } + }, + "public.plugins_tags": { + "name": "plugins_tags", + "schema": "", + "columns": { + "plugin_id": { + "name": "plugin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "plugins_tags_plugin_id_plugins_id_fk": { + "name": "plugins_tags_plugin_id_plugins_id_fk", + "tableFrom": "plugins_tags", + "tableTo": "plugins", + "columnsFrom": ["plugin_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugins_tags_tag_id_tags_id_fk": { + "name": "plugins_tags_tag_id_tags_id_fk", + "tableFrom": "plugins_tags", + "tableTo": "tags", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugins_tags_plugin_id_tag_id_pk": { + "name": "plugins_tags_plugin_id_tag_id_pk", + "columns": ["plugin_id", "tag_id"] + } + }, + "uniqueConstraints": {} + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tags_user_id_users_id_fk": { + "name": "tags_user_id_users_id_fk", + "tableFrom": "tags", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_slug_unique": { + "name": "tags_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + } + }, + "public.file_chunks": { + "name": "file_chunks", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "file_chunks_file_id_files_id_fk": { + "name": "file_chunks_file_id_files_id_fk", + "tableFrom": "file_chunks", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_chunks_chunk_id_chunks_id_fk": { + "name": "file_chunks_chunk_id_chunks_id_fk", + "tableFrom": "file_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "file_chunks_file_id_chunk_id_pk": { + "name": "file_chunks_file_id_chunk_id_pk", + "columns": ["file_id", "chunk_id"] + } + }, + "uniqueConstraints": {} + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_task_id": { + "name": "chunk_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "embedding_task_id": { + "name": "embedding_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "files_user_id_users_id_fk": { + "name": "files_user_id_users_id_fk", + "tableFrom": "files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_file_hash_global_files_hash_id_fk": { + "name": "files_file_hash_global_files_hash_id_fk", + "tableFrom": "files", + "tableTo": "global_files", + "columnsFrom": ["file_hash"], + "columnsTo": ["hash_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "files_chunk_task_id_async_tasks_id_fk": { + "name": "files_chunk_task_id_async_tasks_id_fk", + "tableFrom": "files", + "tableTo": "async_tasks", + "columnsFrom": ["chunk_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "files_embedding_task_id_async_tasks_id_fk": { + "name": "files_embedding_task_id_async_tasks_id_fk", + "tableFrom": "files", + "tableTo": "async_tasks", + "columnsFrom": ["embedding_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.global_files": { + "name": "global_files", + "schema": "", + "columns": { + "hash_id": { + "name": "hash_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.knowledge_base_files": { + "name": "knowledge_base_files", + "schema": "", + "columns": { + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_base_files_knowledge_base_id_knowledge_bases_id_fk": { + "name": "knowledge_base_files_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_files_file_id_files_id_fk": { + "name": "knowledge_base_files_file_id_files_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_base_files_knowledge_base_id_file_id_pk": { + "name": "knowledge_base_files_knowledge_base_id_file_id_pk", + "columns": ["knowledge_base_id", "file_id"] + } + }, + "uniqueConstraints": {} + }, + "public.knowledge_bases": { + "name": "knowledge_bases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_bases_user_id_users_id_fk": { + "name": "knowledge_bases_user_id_users_id_fk", + "tableFrom": "knowledge_bases", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.message_chunks": { + "name": "message_chunks", + "schema": "", + "columns": { + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_chunks_message_id_messages_id_fk": { + "name": "message_chunks_message_id_messages_id_fk", + "tableFrom": "message_chunks", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_chunks_chunk_id_chunks_id_fk": { + "name": "message_chunks_chunk_id_chunks_id_fk", + "tableFrom": "message_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_chunks_chunk_id_message_id_pk": { + "name": "message_chunks_chunk_id_message_id_pk", + "columns": ["chunk_id", "message_id"] + } + }, + "uniqueConstraints": {} + }, + "public.message_plugins": { + "name": "message_plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "api_name": { + "name": "api_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments": { + "name": "arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_plugins_id_messages_id_fk": { + "name": "message_plugins_id_messages_id_fk", + "tableFrom": "message_plugins", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.message_queries": { + "name": "message_queries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rewrite_query": { + "name": "rewrite_query", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embeddings_id": { + "name": "embeddings_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_queries_message_id_messages_id_fk": { + "name": "message_queries_message_id_messages_id_fk", + "tableFrom": "message_queries", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_queries_embeddings_id_embeddings_id_fk": { + "name": "message_queries_embeddings_id_embeddings_id_fk", + "tableFrom": "message_queries", + "tableTo": "embeddings", + "columnsFrom": ["embeddings_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.message_query_chunks": { + "name": "message_query_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_id": { + "name": "query_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "similarity": { + "name": "similarity", + "type": "numeric(6, 5)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_query_chunks_id_messages_id_fk": { + "name": "message_query_chunks_id_messages_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_query_id_message_queries_id_fk": { + "name": "message_query_chunks_query_id_message_queries_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "message_queries", + "columnsFrom": ["query_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_chunk_id_chunks_id_fk": { + "name": "message_query_chunks_chunk_id_chunks_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_query_chunks_chunk_id_id_query_id_pk": { + "name": "message_query_chunks_chunk_id_id_query_id_pk", + "columns": ["chunk_id", "id", "query_id"] + } + }, + "uniqueConstraints": {} + }, + "public.message_tts": { + "name": "message_tts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_md5": { + "name": "content_md5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "voice": { + "name": "voice", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_tts_id_messages_id_fk": { + "name": "message_tts_id_messages_id_fk", + "tableFrom": "message_tts", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_tts_file_id_files_id_fk": { + "name": "message_tts_file_id_files_id_fk", + "tableFrom": "message_tts", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.message_translates": { + "name": "message_translates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from": { + "name": "from", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to": { + "name": "to", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_translates_id_messages_id_fk": { + "name": "message_translates_id_messages_id_fk", + "tableFrom": "message_translates", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observation_id": { + "name": "observation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quota_id": { + "name": "quota_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_created_at_idx": { + "name": "messages_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_client_id_user_unique": { + "name": "message_client_id_user_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_session_id_sessions_id_fk": { + "name": "messages_session_id_sessions_id_fk", + "tableFrom": "messages", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_topic_id_topics_id_fk": { + "name": "messages_topic_id_topics_id_fk", + "tableFrom": "messages", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_parent_id_messages_id_fk": { + "name": "messages_parent_id_messages_id_fk", + "tableFrom": "messages", + "tableTo": "messages", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_quota_id_messages_id_fk": { + "name": "messages_quota_id_messages_id_fk", + "tableFrom": "messages", + "tableTo": "messages", + "columnsFrom": ["quota_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_agent_id_agents_id_fk": { + "name": "messages_agent_id_agents_id_fk", + "tableFrom": "messages", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.messages_files": { + "name": "messages_files", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "messages_files_file_id_files_id_fk": { + "name": "messages_files_file_id_files_id_fk", + "tableFrom": "messages_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_files_message_id_messages_id_fk": { + "name": "messages_files_message_id_messages_id_fk", + "tableFrom": "messages_files", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "messages_files_file_id_message_id_pk": { + "name": "messages_files_file_id_message_id_pk", + "columns": ["file_id", "message_id"] + } + }, + "uniqueConstraints": {} + }, + "public.nextauth_accounts": { + "name": "nextauth_accounts", + "schema": "", + "columns": { + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_accounts_userId_users_id_fk": { + "name": "nextauth_accounts_userId_users_id_fk", + "tableFrom": "nextauth_accounts", + "tableTo": "users", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "nextauth_accounts_provider_providerAccountId_pk": { + "name": "nextauth_accounts_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {} + }, + "public.nextauth_authenticators": { + "name": "nextauth_authenticators", + "schema": "", + "columns": { + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentialBackedUp": { + "name": "credentialBackedUp", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "credentialDeviceType": { + "name": "credentialDeviceType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialPublicKey": { + "name": "credentialPublicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_authenticators_userId_users_id_fk": { + "name": "nextauth_authenticators_userId_users_id_fk", + "tableFrom": "nextauth_authenticators", + "tableTo": "users", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "nextauth_authenticators_userId_credentialID_pk": { + "name": "nextauth_authenticators_userId_credentialID_pk", + "columns": ["userId", "credentialID"] + } + }, + "uniqueConstraints": { + "nextauth_authenticators_credentialID_unique": { + "name": "nextauth_authenticators_credentialID_unique", + "nullsNotDistinct": false, + "columns": ["credentialID"] + } + } + }, + "public.nextauth_sessions": { + "name": "nextauth_sessions", + "schema": "", + "columns": { + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_sessions_userId_users_id_fk": { + "name": "nextauth_sessions_userId_users_id_fk", + "tableFrom": "nextauth_sessions", + "tableTo": "users", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.nextauth_verificationtokens": { + "name": "nextauth_verificationtokens", + "schema": "", + "columns": { + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "nextauth_verificationtokens_identifier_token_pk": { + "name": "nextauth_verificationtokens_identifier_token_pk", + "columns": ["identifier", "token"] + } + }, + "uniqueConstraints": {} + }, + "public.chunks": { + "name": "chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "abstract": { + "name": "abstract", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "chunks_user_id_users_id_fk": { + "name": "chunks_user_id_users_id_fk", + "tableFrom": "chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.embeddings": { + "name": "embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "embeddings": { + "name": "embeddings", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "embeddings_chunk_id_chunks_id_fk": { + "name": "embeddings_chunk_id_chunks_id_fk", + "tableFrom": "embeddings", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embeddings_user_id_users_id_fk": { + "name": "embeddings_user_id_users_id_fk", + "tableFrom": "embeddings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "embeddings_chunk_id_unique": { + "name": "embeddings_chunk_id_unique", + "nullsNotDistinct": false, + "columns": ["chunk_id"] + } + } + }, + "public.unstructured_chunks": { + "name": "unstructured_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "parent_id": { + "name": "parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "composite_id": { + "name": "composite_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "unstructured_chunks_composite_id_chunks_id_fk": { + "name": "unstructured_chunks_composite_id_chunks_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "chunks", + "columnsFrom": ["composite_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "unstructured_chunks_user_id_users_id_fk": { + "name": "unstructured_chunks_user_id_users_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "unstructured_chunks_file_id_files_id_fk": { + "name": "unstructured_chunks_file_id_files_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.rag_eval_dataset_records": { + "name": "rag_eval_dataset_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_dataset_records_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ideal": { + "name": "ideal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_files": { + "name": "reference_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_dataset_records_dataset_id_rag_eval_datasets_id_fk": { + "name": "rag_eval_dataset_records_dataset_id_rag_eval_datasets_id_fk", + "tableFrom": "rag_eval_dataset_records", + "tableTo": "rag_eval_datasets", + "columnsFrom": ["dataset_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_dataset_records_user_id_users_id_fk": { + "name": "rag_eval_dataset_records_user_id_users_id_fk", + "tableFrom": "rag_eval_dataset_records", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.rag_eval_datasets": { + "name": "rag_eval_datasets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_datasets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "30000", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_datasets_knowledge_base_id_knowledge_bases_id_fk": { + "name": "rag_eval_datasets_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "rag_eval_datasets", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_datasets_user_id_users_id_fk": { + "name": "rag_eval_datasets_user_id_users_id_fk", + "tableFrom": "rag_eval_datasets", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.rag_eval_evaluations": { + "name": "rag_eval_evaluations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_evaluations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eval_records_url": { + "name": "eval_records_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_evaluations_dataset_id_rag_eval_datasets_id_fk": { + "name": "rag_eval_evaluations_dataset_id_rag_eval_datasets_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "rag_eval_datasets", + "columnsFrom": ["dataset_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluations_knowledge_base_id_knowledge_bases_id_fk": { + "name": "rag_eval_evaluations_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluations_user_id_users_id_fk": { + "name": "rag_eval_evaluations_user_id_users_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.rag_eval_evaluation_records": { + "name": "rag_eval_evaluation_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_evaluation_records_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "ideal": { + "name": "ideal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "question_embedding_id": { + "name": "question_embedding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "dataset_record_id": { + "name": "dataset_record_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "evaluation_id": { + "name": "evaluation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_evaluation_records_question_embedding_id_embeddings_id_fk": { + "name": "rag_eval_evaluation_records_question_embedding_id_embeddings_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "embeddings", + "columnsFrom": ["question_embedding_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_dataset_record_id_rag_eval_dataset_records_id_fk": { + "name": "rag_eval_evaluation_records_dataset_record_id_rag_eval_dataset_records_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "rag_eval_dataset_records", + "columnsFrom": ["dataset_record_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_evaluation_id_rag_eval_evaluations_id_fk": { + "name": "rag_eval_evaluation_records_evaluation_id_rag_eval_evaluations_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "rag_eval_evaluations", + "columnsFrom": ["evaluation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_user_id_users_id_fk": { + "name": "rag_eval_evaluation_records_user_id_users_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.agents_to_sessions": { + "name": "agents_to_sessions", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "agents_to_sessions_agent_id_agents_id_fk": { + "name": "agents_to_sessions_agent_id_agents_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_to_sessions_session_id_sessions_id_fk": { + "name": "agents_to_sessions_session_id_sessions_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_to_sessions_agent_id_session_id_pk": { + "name": "agents_to_sessions_agent_id_session_id_pk", + "columns": ["agent_id", "session_id"] + } + }, + "uniqueConstraints": {} + }, + "public.files_to_sessions": { + "name": "files_to_sessions", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "files_to_sessions_file_id_files_id_fk": { + "name": "files_to_sessions_file_id_files_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_to_sessions_session_id_sessions_id_fk": { + "name": "files_to_sessions_session_id_sessions_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "files_to_sessions_file_id_session_id_pk": { + "name": "files_to_sessions_file_id_session_id_pk", + "columns": ["file_id", "session_id"] + } + }, + "uniqueConstraints": {} + }, + "public.session_groups": { + "name": "session_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_groups_user_id_users_id_fk": { + "name": "session_groups_user_id_users_id_fk", + "tableFrom": "session_groups", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_group_client_id_user_unique": { + "name": "session_group_client_id_user_unique", + "nullsNotDistinct": false, + "columns": ["client_id", "user_id"] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_color": { + "name": "background_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'agent'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slug_user_id_unique": { + "name": "slug_user_id_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sessions_group_id_session_groups_id_fk": { + "name": "sessions_group_id_session_groups_id_fk", + "tableFrom": "sessions", + "tableTo": "session_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_client_id_user_id_unique": { + "name": "sessions_client_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id", "user_id"] + } + } + }, + "public.topics": { + "name": "topics", + "schema": "", + "columns": { + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "topics_session_id_sessions_id_fk": { + "name": "topics_session_id_sessions_id_fk", + "tableFrom": "topics", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topics_user_id_users_id_fk": { + "name": "topics_user_id_users_id_fk", + "tableFrom": "topics", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "topic_client_id_user_id_unique": { + "name": "topic_client_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id", "user_id"] + } + } + }, + "public.user_installed_plugins": { + "name": "user_installed_plugins", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_params": { + "name": "custom_params", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_installed_plugins_user_id_users_id_fk": { + "name": "user_installed_plugins_user_id_users_id_fk", + "tableFrom": "user_installed_plugins", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_installed_plugins_user_id_identifier_pk": { + "name": "user_installed_plugins_user_id_identifier_pk", + "columns": ["user_id", "identifier"] + } + }, + "uniqueConstraints": {} + }, + "public.user_budgets": { + "name": "user_budgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "free_budget_id": { + "name": "free_budget_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_budget_key": { + "name": "free_budget_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscription_budget_id": { + "name": "subscription_budget_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscription_budget_key": { + "name": "subscription_budget_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_budget_id": { + "name": "package_budget_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_budget_key": { + "name": "package_budget_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_budgets_id_users_id_fk": { + "name": "user_budgets_id_users_id_fk", + "tableFrom": "user_budgets", + "tableTo": "users", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user_settings": { + "name": "user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tts": { + "name": "tts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_vaults": { + "name": "key_vaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "system_agent": { + "name": "system_agent", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "default_agent": { + "name": "default_agent", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool": { + "name": "tool", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_settings_id_users_id_fk": { + "name": "user_settings_id_users_id_fk", + "tableFrom": "user_settings", + "tableTo": "users", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user_subscriptions": { + "name": "user_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing": { + "name": "pricing", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "billing_paid_at": { + "name": "billing_paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "billing_cycle_start": { + "name": "billing_cycle_start", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "billing_cycle_end": { + "name": "billing_cycle_end", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_billing": { + "name": "next_billing", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recurring": { + "name": "recurring", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_limit": { + "name": "storage_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_subscriptions_user_id_users_id_fk": { + "name": "user_subscriptions_user_id_users_id_fk", + "tableFrom": "user_subscriptions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_onboarded": { + "name": "is_onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "clerk_created_at": { + "name": "clerk_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "preference": { + "name": "preference", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + } + } + } + }, + "version": "7" +} diff --git a/src/database/server/migrations/meta/_journal.json b/src/database/server/migrations/meta/_journal.json index 0a360f065826..6d97c2a500cb 100644 --- a/src/database/server/migrations/meta/_journal.json +++ b/src/database/server/migrations/meta/_journal.json @@ -56,6 +56,13 @@ "when": 1724254147447, "tag": "0007_fix_embedding_table", "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1725366565650, + "tag": "0008_add_rag_evals", + "breakpoints": true } ], "version": "6" diff --git a/src/database/server/models/file.ts b/src/database/server/models/file.ts index da416dc8cf92..f26b7e23071c 100644 --- a/src/database/server/models/file.ts +++ b/src/database/server/models/file.ts @@ -1,5 +1,5 @@ -import { asc, count, eq, ilike, inArray, notExists, sum } from 'drizzle-orm'; -import { and, desc } from 'drizzle-orm/expressions'; +import { asc, count, eq, ilike, inArray, notExists, or, sum } from 'drizzle-orm'; +import { and, desc, like } from 'drizzle-orm/expressions'; import { serverDBEnv } from '@/config/db'; import { serverDB } from '@/database/server/core/db'; @@ -280,4 +280,13 @@ export class FileModel { } } }; + + async findByNames(fileNames: string[]) { + return serverDB.query.files.findMany({ + where: and( + or(...fileNames.map((name) => like(files.name, `${name}%`))), + eq(files.userId, this.userId), + ), + }); + } } diff --git a/src/database/server/models/ragEval/dataset.ts b/src/database/server/models/ragEval/dataset.ts new file mode 100644 index 000000000000..2c506fea1845 --- /dev/null +++ b/src/database/server/models/ragEval/dataset.ts @@ -0,0 +1,59 @@ +import { and, desc, eq } from 'drizzle-orm'; + +import { serverDB } from '@/database/server'; +import { NewEvalDatasetsItem, evalDatasets } from '@/database/server/schemas/lobechat'; +import { RAGEvalDataSetItem } from '@/types/eval'; + +export class EvalDatasetModel { + private userId: string; + + constructor(userId: string) { + this.userId = userId; + } + + create = async (params: NewEvalDatasetsItem) => { + const [result] = await serverDB + .insert(evalDatasets) + .values({ ...params, userId: this.userId }) + .returning(); + return result; + }; + + delete = async (id: number) => { + return serverDB + .delete(evalDatasets) + .where(and(eq(evalDatasets.id, id), eq(evalDatasets.userId, this.userId))); + }; + + query = async (knowledgeBaseId: string): Promise<RAGEvalDataSetItem[]> => { + return serverDB + .select({ + createdAt: evalDatasets.createdAt, + description: evalDatasets.description, + id: evalDatasets.id, + name: evalDatasets.name, + updatedAt: evalDatasets.updatedAt, + }) + .from(evalDatasets) + .where( + and( + eq(evalDatasets.userId, this.userId), + eq(evalDatasets.knowledgeBaseId, knowledgeBaseId), + ), + ) + .orderBy(desc(evalDatasets.createdAt)); + }; + + findById = async (id: number) => { + return serverDB.query.evalDatasets.findFirst({ + where: and(eq(evalDatasets.id, id), eq(evalDatasets.userId, this.userId)), + }); + }; + + update = async (id: number, value: Partial<NewEvalDatasetsItem>) => { + return serverDB + .update(evalDatasets) + .set({ ...value, updatedAt: new Date() }) + .where(and(eq(evalDatasets.id, id), eq(evalDatasets.userId, this.userId))); + }; +} diff --git a/src/database/server/models/ragEval/datasetRecord.ts b/src/database/server/models/ragEval/datasetRecord.ts new file mode 100644 index 000000000000..dfd2c3c1189e --- /dev/null +++ b/src/database/server/models/ragEval/datasetRecord.ts @@ -0,0 +1,87 @@ +import { and, eq, inArray } from 'drizzle-orm'; + +import { serverDB } from '@/database/server'; +import { + NewEvalDatasetRecordsItem, + evalDatasetRecords, + files, +} from '@/database/server/schemas/lobechat'; +import { EvalDatasetRecordRefFile } from '@/types/eval'; + +export class EvalDatasetRecordModel { + private userId: string; + + constructor(userId: string) { + this.userId = userId; + } + + create = async (params: NewEvalDatasetRecordsItem) => { + const [result] = await serverDB + .insert(evalDatasetRecords) + .values({ ...params, userId: this.userId }) + .returning(); + return result; + }; + + batchCreate = async (params: NewEvalDatasetRecordsItem[]) => { + const [result] = await serverDB + .insert(evalDatasetRecords) + .values(params.map((item) => ({ ...item, userId: this.userId }))) + .returning(); + + return result; + }; + + delete = async (id: number) => { + return serverDB + .delete(evalDatasetRecords) + .where(and(eq(evalDatasetRecords.id, id), eq(evalDatasetRecords.userId, this.userId))); + }; + + query = async (datasetId: number) => { + const list = await serverDB.query.evalDatasetRecords.findMany({ + where: and( + eq(evalDatasetRecords.datasetId, datasetId), + eq(evalDatasetRecords.userId, this.userId), + ), + }); + const fileList = list.flatMap((item) => item.referenceFiles).filter(Boolean) as string[]; + + const fileItems = await serverDB + .select({ fileType: files.fileType, id: files.id, name: files.name }) + .from(files) + .where(and(inArray(files.id, fileList), eq(files.userId, this.userId))) + .execute(); + + return list.map((item) => { + return { + ...item, + referenceFiles: (item.referenceFiles?.map((fileId) => { + return fileItems.find((file) => file.id === fileId); + }) || []) as EvalDatasetRecordRefFile[], + }; + }); + }; + + findByDatasetId = async (datasetId: number) => { + return serverDB.query.evalDatasetRecords.findMany({ + where: and( + eq(evalDatasetRecords.datasetId, datasetId), + eq(evalDatasetRecords.userId, this.userId), + ), + }); + }; + + findById = async (id: number) => { + return serverDB.query.evalDatasetRecords.findFirst({ + where: and(eq(evalDatasetRecords.id, id), eq(evalDatasetRecords.userId, this.userId)), + }); + }; + + update = async (id: number, value: Partial<NewEvalDatasetRecordsItem>) => { + return serverDB + .update(evalDatasetRecords) + .set(value) + .where(and(eq(evalDatasetRecords.id, id), eq(evalDatasetRecords.userId, this.userId))); + }; +} diff --git a/src/database/server/models/ragEval/evaluation.ts b/src/database/server/models/ragEval/evaluation.ts new file mode 100644 index 000000000000..a543aaa5a200 --- /dev/null +++ b/src/database/server/models/ragEval/evaluation.ts @@ -0,0 +1,96 @@ +import { SQL, and, count, desc, eq, inArray } from 'drizzle-orm'; + +import { serverDB } from '@/database/server'; +import { + NewEvalEvaluationItem, + evalDatasets, + evalEvaluation, + evaluationRecords, +} from '@/database/server/schemas/lobechat'; +import { EvalEvaluationStatus, RAGEvalEvaluationItem } from '@/types/eval'; + +export class EvalEvaluationModel { + private userId: string; + + constructor(userId: string) { + this.userId = userId; + } + + create = async (params: NewEvalEvaluationItem) => { + const [result] = await serverDB + .insert(evalEvaluation) + .values({ ...params, userId: this.userId }) + .returning(); + return result; + }; + + delete = async (id: number) => { + return serverDB + .delete(evalEvaluation) + .where(and(eq(evalEvaluation.id, id), eq(evalEvaluation.userId, this.userId))); + }; + + queryByKnowledgeBaseId = async (knowledgeBaseId: string) => { + const evaluations = await serverDB + .select({ + createdAt: evalEvaluation.createdAt, + dataset: { + id: evalDatasets.id, + name: evalDatasets.name, + }, + evalRecordsUrl: evalEvaluation.evalRecordsUrl, + id: evalEvaluation.id, + name: evalEvaluation.name, + status: evalEvaluation.status, + updatedAt: evalEvaluation.updatedAt, + }) + .from(evalEvaluation) + .leftJoin(evalDatasets, eq(evalDatasets.id, evalEvaluation.datasetId)) + .orderBy(desc(evalEvaluation.createdAt)) + .where( + and( + eq(evalEvaluation.userId, this.userId), + eq(evalEvaluation.knowledgeBaseId, knowledgeBaseId), + ), + ); + + // 然后查询每个评估的记录统计 + const evaluationIds = evaluations.map((evals) => evals.id); + + const recordStats = await serverDB + .select({ + evaluationId: evaluationRecords.evaluationId, + success: count(evaluationRecords.status).if( + eq(evaluationRecords.status, EvalEvaluationStatus.Success), + ) as SQL<number>, + total: count(), + }) + .from(evaluationRecords) + .where(inArray(evaluationRecords.evaluationId, evaluationIds)) + .groupBy(evaluationRecords.evaluationId); + + return evaluations.map((evaluation) => { + const stats = recordStats.find((stat) => stat.evaluationId === evaluation.id); + + return { + ...evaluation, + recordsStats: stats + ? { success: Number(stats.success), total: Number(stats.total) } + : { success: 0, total: 0 }, + } as RAGEvalEvaluationItem; + }); + }; + + findById = async (id: number) => { + return serverDB.query.evalEvaluation.findFirst({ + where: and(eq(evalEvaluation.id, id), eq(evalEvaluation.userId, this.userId)), + }); + }; + + update = async (id: number, value: Partial<NewEvalEvaluationItem>) => { + return serverDB + .update(evalEvaluation) + .set(value) + .where(and(eq(evalEvaluation.id, id), eq(evalEvaluation.userId, this.userId))); + }; +} diff --git a/src/database/server/models/ragEval/evaluationRecord.ts b/src/database/server/models/ragEval/evaluationRecord.ts new file mode 100644 index 000000000000..b8a7374697a1 --- /dev/null +++ b/src/database/server/models/ragEval/evaluationRecord.ts @@ -0,0 +1,64 @@ +import { and, eq } from 'drizzle-orm'; + +import { serverDB } from '@/database/server'; +import { NewEvaluationRecordsItem, evaluationRecords } from '@/database/server/schemas/lobechat'; + +export class EvaluationRecordModel { + private userId: string; + + constructor(userId: string) { + this.userId = userId; + } + + create = async (params: NewEvaluationRecordsItem) => { + const [result] = await serverDB + .insert(evaluationRecords) + .values({ ...params, userId: this.userId }) + .returning(); + return result; + }; + + batchCreate = async (params: NewEvaluationRecordsItem[]) => { + return serverDB + .insert(evaluationRecords) + .values(params.map((item) => ({ ...item, userId: this.userId }))) + .returning(); + }; + + delete = async (id: number) => { + return serverDB + .delete(evaluationRecords) + .where(and(eq(evaluationRecords.id, id), eq(evaluationRecords.userId, this.userId))); + }; + + query = async (reportId: number) => { + return serverDB.query.evaluationRecords.findMany({ + where: and( + eq(evaluationRecords.evaluationId, reportId), + eq(evaluationRecords.userId, this.userId), + ), + }); + }; + + findById = async (id: number) => { + return serverDB.query.evaluationRecords.findFirst({ + where: and(eq(evaluationRecords.id, id), eq(evaluationRecords.userId, this.userId)), + }); + }; + + findByEvaluationId = async (evaluationId: number) => { + return serverDB.query.evaluationRecords.findMany({ + where: and( + eq(evaluationRecords.evaluationId, evaluationId), + eq(evaluationRecords.userId, this.userId), + ), + }); + }; + + update = async (id: number, value: Partial<NewEvaluationRecordsItem>) => { + return serverDB + .update(evaluationRecords) + .set(value) + .where(and(eq(evaluationRecords.id, id), eq(evaluationRecords.userId, this.userId))); + }; +} diff --git a/src/database/server/models/ragEval/index.ts b/src/database/server/models/ragEval/index.ts new file mode 100644 index 000000000000..0a5b980d77a0 --- /dev/null +++ b/src/database/server/models/ragEval/index.ts @@ -0,0 +1,4 @@ +export * from './dataset'; +export * from './datasetRecord'; +export * from './evaluation'; +export * from './evaluationRecord'; diff --git a/src/database/server/schemas/lobechat/asyncTask.ts b/src/database/server/schemas/lobechat/asyncTask.ts new file mode 100644 index 000000000000..b825f67106aa --- /dev/null +++ b/src/database/server/schemas/lobechat/asyncTask.ts @@ -0,0 +1,24 @@ +/* eslint-disable sort-keys-fix/sort-keys-fix */ +import { integer, jsonb, pgTable, text, uuid } from 'drizzle-orm/pg-core'; + +import { createdAt, updatedAt } from './_helpers'; +import { users } from './user'; + +export const asyncTasks = pgTable('async_tasks', { + id: uuid('id').defaultRandom().primaryKey(), + type: text('type'), + + status: text('status'), + error: jsonb('error'), + + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), + duration: integer('duration'), + + createdAt: createdAt(), + updatedAt: updatedAt(), +}); + +export type NewAsyncTaskItem = typeof asyncTasks.$inferInsert; +export type AsyncTaskSelectItem = typeof asyncTasks.$inferSelect; diff --git a/src/database/server/schemas/lobechat/file.ts b/src/database/server/schemas/lobechat/file.ts index 6cd5b08e8a81..5907752a1317 100644 --- a/src/database/server/schemas/lobechat/file.ts +++ b/src/database/server/schemas/lobechat/file.ts @@ -11,28 +11,12 @@ import { } from 'drizzle-orm/pg-core'; import { createInsertSchema } from 'drizzle-zod'; -import { chunks } from '@/database/server/schemas/lobechat/rag'; - import { idGenerator } from '../../utils/idGenerator'; import { createdAt, updatedAt } from './_helpers'; +import { asyncTasks } from './asyncTask'; +import { chunks } from './rag'; import { users } from './user'; -export const asyncTasks = pgTable('async_tasks', { - id: uuid('id').defaultRandom().primaryKey(), - type: text('type'), - status: text('status'), - error: jsonb('error'), - userId: text('user_id') - .references(() => users.id, { onDelete: 'cascade' }) - .notNull(), - duration: integer('duration'), - createdAt: createdAt(), - updatedAt: updatedAt(), -}); - -export type NewAsyncTaskItem = typeof asyncTasks.$inferInsert; -export type AsyncTaskSelectItem = typeof asyncTasks.$inferSelect; - export const globalFiles = pgTable('global_files', { hashId: varchar('hash_id', { length: 64 }).primaryKey(), fileType: varchar('file_type', { length: 255 }).notNull(), diff --git a/src/database/server/schemas/lobechat/index.ts b/src/database/server/schemas/lobechat/index.ts index 81f5db459afe..859f205dd7a1 100644 --- a/src/database/server/schemas/lobechat/index.ts +++ b/src/database/server/schemas/lobechat/index.ts @@ -1,9 +1,11 @@ export * from './agent'; +export * from './asyncTask'; export * from './discover'; export * from './file'; export * from './message'; export * from './nextauth'; export * from './rag'; +export * from './ragEvals'; export * from './relations'; export * from './session'; export * from './topic'; diff --git a/src/database/server/schemas/lobechat/ragEvals.ts b/src/database/server/schemas/lobechat/ragEvals.ts new file mode 100644 index 000000000000..6fa6b996dd36 --- /dev/null +++ b/src/database/server/schemas/lobechat/ragEvals.ts @@ -0,0 +1,105 @@ +/* eslint-disable sort-keys-fix/sort-keys-fix */ +import { integer, jsonb, pgTable, text, uuid } from 'drizzle-orm/pg-core'; + +import { DEFAULT_EMBEDDING_MODEL, DEFAULT_MODEL } from '@/const/settings'; +import { EvalEvaluationStatus } from '@/types/eval'; + +import { createdAt, updatedAt } from './_helpers'; +import { knowledgeBases } from './file'; +import { embeddings } from './rag'; +import { users } from './user'; + +export const evalDatasets = pgTable('rag_eval_datasets', { + id: integer('id').generatedAlwaysAsIdentity({ startWith: 30_000 }).primaryKey(), + + description: text('description'), + name: text('name').notNull(), + + knowledgeBaseId: text('knowledge_base_id').references(() => knowledgeBases.id, { + onDelete: 'cascade', + }), + userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }), + + updatedAt: updatedAt(), + createdAt: createdAt(), +}); + +export type NewEvalDatasetsItem = typeof evalDatasets.$inferInsert; +export type EvalDatasetsSelectItem = typeof evalDatasets.$inferSelect; + +export const evalDatasetRecords = pgTable('rag_eval_dataset_records', { + id: integer('id').generatedAlwaysAsIdentity().primaryKey(), + datasetId: integer('dataset_id') + .references(() => evalDatasets.id, { onDelete: 'cascade' }) + .notNull(), + + ideal: text('ideal'), + question: text('question'), + referenceFiles: text('reference_files').array(), + metadata: jsonb('metadata'), + + userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }), + createdAt: createdAt(), +}); + +export type NewEvalDatasetRecordsItem = typeof evalDatasetRecords.$inferInsert; +export type EvalDatasetRecordsSelectItem = typeof evalDatasetRecords.$inferSelect; + +export const evalEvaluation = pgTable('rag_eval_evaluations', { + id: integer('id').generatedAlwaysAsIdentity().primaryKey(), + name: text('name').notNull(), + description: text('description'), + + evalRecordsUrl: text('eval_records_url'), + status: text('status').$defaultFn(() => EvalEvaluationStatus.Pending), + error: jsonb('error'), + + datasetId: integer('dataset_id') + .references(() => evalDatasets.id, { onDelete: 'cascade' }) + .notNull(), + knowledgeBaseId: text('knowledge_base_id').references(() => knowledgeBases.id, { + onDelete: 'cascade', + }), + languageModel: text('language_model').$defaultFn(() => DEFAULT_MODEL), + embeddingModel: text('embedding_model').$defaultFn(() => DEFAULT_EMBEDDING_MODEL), + + userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }), + createdAt: createdAt(), + updatedAt: updatedAt(), +}); + +export type NewEvalEvaluationItem = typeof evalEvaluation.$inferInsert; +export type EvalEvaluationSelectItem = typeof evalEvaluation.$inferSelect; + +export const evaluationRecords = pgTable('rag_eval_evaluation_records', { + id: integer('id').generatedAlwaysAsIdentity().primaryKey(), + + question: text('question').notNull(), + answer: text('answer'), + context: text('context').array(), + ideal: text('ideal'), + + status: text('status').$defaultFn(() => EvalEvaluationStatus.Pending), + error: jsonb('error'), + + languageModel: text('language_model'), + embeddingModel: text('embedding_model'), + + questionEmbeddingId: uuid('question_embedding_id').references(() => embeddings.id, { + onDelete: 'set null', + }), + + duration: integer('duration'), + datasetRecordId: integer('dataset_record_id') + .references(() => evalDatasetRecords.id, { onDelete: 'cascade' }) + .notNull(), + evaluationId: integer('evaluation_id') + .references(() => evalEvaluation.id, { onDelete: 'cascade' }) + .notNull(), + + userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }), + createdAt: createdAt(), +}); + +export type NewEvaluationRecordsItem = typeof evaluationRecords.$inferInsert; +export type EvaluationRecordsSelectItem = typeof evaluationRecords.$inferSelect; diff --git a/src/database/server/schemas/lobechat/relations.ts b/src/database/server/schemas/lobechat/relations.ts index 65d4926ab15d..f4b8c399ae41 100644 --- a/src/database/server/schemas/lobechat/relations.ts +++ b/src/database/server/schemas/lobechat/relations.ts @@ -3,8 +3,9 @@ import { relations } from 'drizzle-orm'; import { pgTable, primaryKey, text } from 'drizzle-orm/pg-core'; import { agents, agentsFiles, agentsKnowledgeBases } from './agent'; +import { asyncTasks } from './asyncTask'; import { agentsTags, plugins, pluginsTags, tags } from './discover'; -import { asyncTasks, files, knowledgeBases } from './file'; +import { files, knowledgeBases } from './file'; import { messages, messagesFiles } from './message'; import { unstructuredChunks } from './rag'; import { sessionGroups, sessions } from './session'; diff --git a/src/libs/agent-runtime/types/chat.ts b/src/libs/agent-runtime/types/chat.ts index 09ea55961a8c..5f7976663483 100644 --- a/src/libs/agent-runtime/types/chat.ts +++ b/src/libs/agent-runtime/types/chat.ts @@ -72,6 +72,8 @@ export interface ChatStreamPayload { * @default openai */ provider?: string; + + responseMode?: 'streamText' | 'json'; /** * @title 是否开启流式请求 * @default true @@ -84,6 +86,7 @@ export interface ChatStreamPayload { temperature: number; tool_choice?: string; tools?: ChatCompletionTool[]; + /** * @title 控制生成文本中最高概率的单个令牌 * @default 1 diff --git a/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts b/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts index 3b8f8f348849..878cd5a92835 100644 --- a/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +++ b/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts @@ -149,7 +149,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any> this.baseURL = this.client.baseURL; } - async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) { + async chat({ responseMode, ...payload }: ChatStreamPayload, options?: ChatCompetitionOptions) { try { const postPayload = chatCompletion?.handlePayload ? chatCompletion.handlePayload(payload, this._options) @@ -186,6 +186,8 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any> debugResponse(response); } + if (responseMode === 'json') return Response.json(response); + const stream = transformResponseToStream(response as unknown as OpenAI.ChatCompletion); return StreamingResponse(OpenAIStream(stream, options?.callback), { diff --git a/src/libs/langchain/loaders/index.ts b/src/libs/langchain/loaders/index.ts index 89b548be5771..df9884d2038f 100644 --- a/src/libs/langchain/loaders/index.ts +++ b/src/libs/langchain/loaders/index.ts @@ -27,7 +27,7 @@ export class ChunkingLoader { const fileBlob = new Blob([Buffer.from(content)]); const txt = this.uint8ArrayToString(content); - const type = this.getType(filename); + const type = this.getType(filename?.toLowerCase()); switch (type) { case 'code': { diff --git a/src/locales/default/index.ts b/src/locales/default/index.ts index 8ba1d0bc2c54..5da78893dd7d 100644 --- a/src/locales/default/index.ts +++ b/src/locales/default/index.ts @@ -13,6 +13,7 @@ import migration from './migration'; import modelProvider from './modelProvider'; import plugin from './plugin'; import portal from './portal'; +import ragEval from './ragEval'; import setting from './setting'; import welcome from './welcome'; @@ -31,6 +32,7 @@ const resources = { modelProvider, plugin, portal, + ragEval, setting, tool, welcome, diff --git a/src/locales/default/knowledgeBase.ts b/src/locales/default/knowledgeBase.ts index 6f36c4c496fc..adacf8814423 100644 --- a/src/locales/default/knowledgeBase.ts +++ b/src/locales/default/knowledgeBase.ts @@ -23,6 +23,7 @@ export default { title: '新建知识库', }, tab: { + evals: '评测', files: '文档', settings: '设置', testing: '召回测试', diff --git a/src/locales/default/ragEval.ts b/src/locales/default/ragEval.ts new file mode 100644 index 000000000000..d54e6ff0253d --- /dev/null +++ b/src/locales/default/ragEval.ts @@ -0,0 +1,93 @@ +export default { + addDataset: { + confirm: '新建', + description: { + placeholder: '数据集简介(选填)', + }, + name: { + placeholder: '数据集名称', + required: '请填写数据集名称', + }, + title: '添加数据集', + }, + dataset: { + addNewButton: '创建数据集', + emptyGuide: '当前数据集为空,请创建一个数据集。', + list: { + table: { + actions: { + importData: '导入数据', + }, + columns: { + actions: '操作', + ideal: { + title: '期望回答', + }, + question: { + title: '问题', + }, + referenceFiles: { + title: '参考文件', + }, + }, + notSelected: '请在左侧选择数据集', + title: '数据集详情', + }, + title: '数据集', + }, + }, + evaluation: { + addEvaluation: { + confirm: '新建', + datasetId: { + placeholder: '请选择你的评测数据集', + required: '请选择评测数据集', + }, + description: { + placeholder: '评测任务简介(选填)', + }, + name: { + placeholder: '评测任务名称', + required: '请填写评测任务名称', + }, + title: '添加评测任务', + }, + addNewButton: '创建评测', + emptyGuide: '当前评测任务为空,开始创建评测。', + + table: { + columns: { + actions: { + checkStatus: '检查状态', + confirmDelete: '是否删除本条评测', + confirmRun: + '是否开始运行?开始运行后将在后台异步执行评测任务,关闭页面不影响异步任务的执行', + downloadRecords: '下载评测', + retry: '重试', + run: '运行', + title: '操作', + }, + datasetId: { + title: '数据集', + }, + name: { + title: '评测任务名称', + }, + records: { + title: '评测记录数', + }, + referenceFiles: { + title: '参考文件', + }, + status: { + error: '执行出错', + pending: '待运行', + processing: '运行中', + success: '执行成功', + title: '状态', + }, + }, + title: '评测任务列表', + }, + }, +}; diff --git a/src/server/modules/S3/index.ts b/src/server/modules/S3/index.ts index 5bf0c9bd0566..ad02d9e59348 100644 --- a/src/server/modules/S3/index.ts +++ b/src/server/modules/S3/index.ts @@ -104,4 +104,15 @@ export class S3 { return getSignedUrl(this.client, command, { expiresIn: 3600 }); } + + public async uploadContent(path: string, content: string) { + const command = new PutObjectCommand({ + ACL: this.setAcl ? 'public-read' : undefined, + Body: content, + Bucket: this.bucket, + Key: path, + }); + + return this.client.send(command); + } } diff --git a/src/server/routers/async/index.ts b/src/server/routers/async/index.ts index 27463d1b9c1a..f87b3f90c401 100644 --- a/src/server/routers/async/index.ts +++ b/src/server/routers/async/index.ts @@ -1,10 +1,12 @@ import { publicProcedure, asyncRouter as router } from '@/libs/trpc/async'; import { fileRouter } from './file'; +import { ragEvalRouter } from './ragEval'; export const asyncRouter = router({ file: fileRouter, healthcheck: publicProcedure.query(() => "i'm live!"), + ragEval: ragEvalRouter, }); export type AsyncRouter = typeof asyncRouter; diff --git a/src/server/routers/async/ragEval.ts b/src/server/routers/async/ragEval.ts new file mode 100644 index 000000000000..949edfdba170 --- /dev/null +++ b/src/server/routers/async/ragEval.ts @@ -0,0 +1,138 @@ +import { TRPCError } from '@trpc/server'; +import OpenAI from 'openai'; +import { z } from 'zod'; + +import { initAgentRuntimeWithUserPayload } from '@/app/api/chat/agentRuntime'; +import { chainAnswerWithContext } from '@/chains/answerWithContext'; +import { DEFAULT_EMBEDDING_MODEL, DEFAULT_MODEL } from '@/const/settings'; +import { ChunkModel } from '@/database/server/models/chunk'; +import { EmbeddingModel } from '@/database/server/models/embedding'; +import { FileModel } from '@/database/server/models/file'; +import { + EvalDatasetRecordModel, + EvalEvaluationModel, + EvaluationRecordModel, +} from '@/database/server/models/ragEval'; +import { ModelProvider } from '@/libs/agent-runtime'; +import { asyncAuthedProcedure, asyncRouter as router } from '@/libs/trpc/async'; +import { ChunkService } from '@/server/services/chunk'; +import { AsyncTaskError } from '@/types/asyncTask'; +import { EvalEvaluationStatus } from '@/types/eval'; + +const ragEvalProcedure = asyncAuthedProcedure.use(async (opts) => { + const { ctx } = opts; + + return opts.next({ + ctx: { + chunkModel: new ChunkModel(ctx.userId), + chunkService: new ChunkService(ctx.userId), + datasetRecordModel: new EvalDatasetRecordModel(ctx.userId), + embeddingModel: new EmbeddingModel(ctx.userId), + evalRecordModel: new EvaluationRecordModel(ctx.userId), + evaluationModel: new EvalEvaluationModel(ctx.userId), + fileModel: new FileModel(ctx.userId), + }, + }); +}); + +export const ragEvalRouter = router({ + runRecordEvaluation: ragEvalProcedure + .input( + z.object({ + evalRecordId: z.number(), + }), + ) + .mutation(async ({ ctx, input }) => { + const evalRecord = await ctx.evalRecordModel.findById(input.evalRecordId); + + if (!evalRecord) { + throw new TRPCError({ code: 'BAD_REQUEST', message: 'Evaluation not found' }); + } + + const now = Date.now(); + try { + const agentRuntime = await initAgentRuntimeWithUserPayload( + ModelProvider.OpenAI, + ctx.jwtPayload, + ); + + const { question, languageModel, embeddingModel } = evalRecord; + + let questionEmbeddingId = evalRecord.questionEmbeddingId; + let context = evalRecord.context; + + // 如果不存在 questionEmbeddingId,那么就需要做一次 embedding + if (!questionEmbeddingId) { + const embeddings = await agentRuntime.embeddings({ + dimensions: 1024, + input: question, + model: !!embeddingModel ? embeddingModel : DEFAULT_EMBEDDING_MODEL, + }); + + const embeddingId = await ctx.embeddingModel.create({ + embeddings: embeddings?.[0].embedding, + model: embeddingModel, + }); + + await ctx.evalRecordModel.update(evalRecord.id, { + questionEmbeddingId: embeddingId, + }); + + questionEmbeddingId = embeddingId; + } + + // 如果不存在 context,那么就需要做一次检索 + if (!context || context.length === 0) { + const datasetRecord = await ctx.datasetRecordModel.findById(evalRecord.datasetRecordId); + + const embeddingItem = await ctx.embeddingModel.findById(questionEmbeddingId); + + const chunks = await ctx.chunkModel.semanticSearchForChat({ + embedding: embeddingItem!.embeddings!, + fileIds: datasetRecord!.referenceFiles!, + query: evalRecord.question, + }); + + context = chunks.map((item) => item.text).filter(Boolean) as string[]; + await ctx.evalRecordModel.update(evalRecord.id, { context }); + } + + // 做一次生成 LLM 答案生成 + const { messages } = chainAnswerWithContext({ context, knowledge: [], question }); + + const response = await agentRuntime.chat({ + messages: messages!, + model: !!languageModel ? languageModel : DEFAULT_MODEL, + responseMode: 'json', + stream: false, + temperature: 1, + }); + + const data = (await response.json()) as OpenAI.ChatCompletion; + + const answer = data.choices[0].message.content; + + await ctx.evalRecordModel.update(input.evalRecordId, { + answer, + duration: Date.now() - now, + languageModel, + status: EvalEvaluationStatus.Success, + }); + + return { success: true }; + } catch (e) { + await ctx.evalRecordModel.update(input.evalRecordId, { + error: new AsyncTaskError((e as Error).name, (e as Error).message), + status: EvalEvaluationStatus.Error, + }); + + await ctx.evaluationModel.update(evalRecord.evaluationId, { + status: EvalEvaluationStatus.Error, + }); + + console.error('[RAGEvaluation] error', e); + + return { success: false }; + } + }), +}); diff --git a/src/server/routers/lambda/index.ts b/src/server/routers/lambda/index.ts index 372a1b006422..c8fba4a6f2e2 100644 --- a/src/server/routers/lambda/index.ts +++ b/src/server/routers/lambda/index.ts @@ -5,12 +5,12 @@ import { publicProcedure, router } from '@/libs/trpc'; import { agentRouter } from './agent'; import { chunkRouter } from './chunk'; -// router that connect to db import { fileRouter } from './file'; import { importerRouter } from './importer'; import { knowledgeBaseRouter } from './knowledgeBase'; import { messageRouter } from './message'; import { pluginRouter } from './plugin'; +import { ragEvalRouter } from './ragEval'; import { sessionRouter } from './session'; import { sessionGroupRouter } from './sessionGroup'; import { topicRouter } from './topic'; @@ -25,6 +25,7 @@ export const lambdaRouter = router({ knowledgeBase: knowledgeBaseRouter, message: messageRouter, plugin: pluginRouter, + ragEval: ragEvalRouter, session: sessionRouter, sessionGroup: sessionGroupRouter, topic: topicRouter, diff --git a/src/server/routers/lambda/ragEval.ts b/src/server/routers/lambda/ragEval.ts new file mode 100644 index 000000000000..07a5c54f526f --- /dev/null +++ b/src/server/routers/lambda/ragEval.ts @@ -0,0 +1,296 @@ +/* eslint-disable sort-keys-fix/sort-keys-fix */ +import { TRPCError } from '@trpc/server'; +import dayjs from 'dayjs'; +import JSONL from 'jsonl-parse-stringify'; +import pMap from 'p-map'; +import { z } from 'zod'; + +import { DEFAULT_EMBEDDING_MODEL, DEFAULT_MODEL } from '@/const/settings'; +import { FileModel } from '@/database/server/models/file'; +import { + EvalDatasetModel, + EvalDatasetRecordModel, + EvalEvaluationModel, + EvaluationRecordModel, +} from '@/database/server/models/ragEval'; +import { authedProcedure, router } from '@/libs/trpc'; +import { keyVaults } from '@/libs/trpc/middleware/keyVaults'; +import { S3 } from '@/server/modules/S3'; +import { createAsyncServerClient } from '@/server/routers/async'; +import { getFullFileUrl } from '@/server/utils/files'; +import { + EvalDatasetRecord, + EvalEvaluationStatus, + InsertEvalDatasetRecord, + RAGEvalDataSetItem, + insertEvalDatasetRecordSchema, + insertEvalDatasetsSchema, + insertEvalEvaluationSchema, +} from '@/types/eval'; + +const ragEvalProcedure = authedProcedure.use(keyVaults).use(async (opts) => { + const { ctx } = opts; + + return opts.next({ + ctx: { + datasetModel: new EvalDatasetModel(ctx.userId), + fileModel: new FileModel(ctx.userId), + datasetRecordModel: new EvalDatasetRecordModel(ctx.userId), + evaluationModel: new EvalEvaluationModel(ctx.userId), + evaluationRecordModel: new EvaluationRecordModel(ctx.userId), + s3: new S3(), + }, + }); +}); + +export const ragEvalRouter = router({ + createDataset: ragEvalProcedure + .input( + z.object({ + description: z.string().optional(), + knowledgeBaseId: z.string(), + name: z.string(), + }), + ) + .mutation(async ({ input, ctx }) => { + const data = await ctx.datasetModel.create({ + description: input.description, + knowledgeBaseId: input.knowledgeBaseId, + name: input.name, + }); + + return data?.id; + }), + + getDatasets: ragEvalProcedure + .input(z.object({ knowledgeBaseId: z.string() })) + + .query(async ({ ctx, input }): Promise<RAGEvalDataSetItem[]> => { + return ctx.datasetModel.query(input.knowledgeBaseId); + }), + + removeDataset: ragEvalProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input, ctx }) => { + return ctx.datasetModel.delete(input.id); + }), + + updateDataset: ragEvalProcedure + .input( + z.object({ + id: z.number(), + value: insertEvalDatasetsSchema.partial(), + }), + ) + .mutation(async ({ input, ctx }) => { + return ctx.datasetModel.update(input.id, input.value); + }), + + // Dataset Item operations + createDatasetRecords: ragEvalProcedure + .input( + z.object({ + datasetId: z.number(), + question: z.string(), + ideal: z.string().optional(), + referenceFiles: z.array(z.string()).optional(), + metadata: z.record(z.unknown()).optional(), + }), + ) + .mutation(async ({ input, ctx }) => { + const data = await ctx.datasetRecordModel.create(input); + return data?.id; + }), + + getDatasetRecords: ragEvalProcedure + .input(z.object({ datasetId: z.number() })) + .query(async ({ ctx, input }): Promise<EvalDatasetRecord[]> => { + return ctx.datasetRecordModel.query(input.datasetId); + }), + + removeDatasetRecords: ragEvalProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input, ctx }) => { + return ctx.datasetRecordModel.delete(input.id); + }), + + updateDatasetRecords: ragEvalProcedure + .input( + z.object({ + id: z.number(), + value: z + .object({ + question: z.string(), + ideal: z.string(), + referenceFiles: z.array(z.string()), + metadata: z.record(z.unknown()), + }) + .partial(), + }), + ) + .mutation(async ({ input, ctx }) => { + return ctx.datasetRecordModel.update(input.id, input.value); + }), + + importDatasetRecords: ragEvalProcedure + .input( + z.object({ + datasetId: z.number(), + pathname: z.string(), + }), + ) + .mutation(async ({ input, ctx }) => { + const dataStr = await ctx.s3.getFileContent(input.pathname); + const items = JSONL.parse<InsertEvalDatasetRecord>(dataStr); + + insertEvalDatasetRecordSchema.array().parse(items); + + const data = await Promise.all( + items.map(async ({ referenceFiles, question, ideal }) => { + const files = typeof referenceFiles === 'string' ? [referenceFiles] : referenceFiles; + + let fileIds: string[] | undefined = undefined; + + if (files) { + const items = await ctx.fileModel.findByNames(files); + + fileIds = items.map((item) => item.id); + } + + return { + question, + ideal, + referenceFiles: fileIds, + datasetId: input.datasetId, + }; + }), + ); + + return ctx.datasetRecordModel.batchCreate(data); + }), + + // Evaluation operations + startEvaluationTask: ragEvalProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input, ctx }) => { + // Start evaluation task + const evaluation = await ctx.evaluationModel.findById(input.id); + + if (!evaluation) { + throw new TRPCError({ code: 'BAD_REQUEST', message: 'Evaluation not found' }); + } + + // create evaluation records by dataset records + const datasetRecords = await ctx.datasetRecordModel.findByDatasetId(evaluation.datasetId); + + if (datasetRecords.length === 0) { + throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dataset record is empty' }); + } + + const evalRecords = await ctx.evaluationRecordModel.batchCreate( + datasetRecords.map((record) => ({ + evaluationId: input.id, + datasetRecordId: record.id, + question: record.question!, + ideal: record.ideal, + status: EvalEvaluationStatus.Pending, + embeddingModel: DEFAULT_EMBEDDING_MODEL, + languageModel: DEFAULT_MODEL, + })), + ); + + const asyncCaller = await createAsyncServerClient(ctx.userId, ctx.jwtPayload); + + await ctx.evaluationModel.update(input.id, { status: EvalEvaluationStatus.Processing }); + try { + await pMap( + evalRecords, + async (record) => { + asyncCaller.ragEval.runRecordEvaluation + .mutate({ evalRecordId: record.id }) + .catch(async (e) => { + await ctx.evaluationModel.update(input.id, { status: EvalEvaluationStatus.Error }); + + throw new TRPCError({ + code: 'BAD_GATEWAY', + message: `[ASYNC_TASK] Failed to start evaluation task: ${e.message}`, + }); + }); + }, + { + concurrency: 30, + }, + ); + + return { success: true }; + } catch (e) { + console.error('[startEvaluationTask]:', e); + + await ctx.evaluationModel.update(input.id, { status: EvalEvaluationStatus.Error }); + + return { success: false }; + } + }), + + checkEvaluationStatus: ragEvalProcedure + .input(z.object({ id: z.number() })) + .query(async ({ input, ctx }) => { + const evaluation = await ctx.evaluationModel.findById(input.id); + + if (!evaluation) { + throw new TRPCError({ code: 'BAD_REQUEST', message: 'Evaluation not found' }); + } + + const records = await ctx.evaluationRecordModel.findByEvaluationId(input.id); + + const isSuccess = records.every((record) => record.status === EvalEvaluationStatus.Success); + + if (isSuccess) { + // 将结果上传到 S3 + + const evalRecords = records.map((record) => ({ + question: record.question, + context: record.context, + answer: record.answer, + ground_truth: record.ideal, + })); + const date = dayjs().format('YYYY-MM-DD-HH-mm'); + const filename = `${date}-eval_${evaluation.id}-${evaluation.name}.jsonl`; + const path = `rag_eval_records/${filename}`; + + await ctx.s3.uploadContent(path, JSONL.stringify(evalRecords)); + + // 保存数据 + await ctx.evaluationModel.update(input.id, { + status: EvalEvaluationStatus.Success, + evalRecordsUrl: getFullFileUrl(path), + }); + } + + return { success: isSuccess }; + }), + createEvaluation: ragEvalProcedure + .input(insertEvalEvaluationSchema) + .mutation(async ({ input, ctx }) => { + const data = await ctx.evaluationModel.create({ + description: input.description, + knowledgeBaseId: input.knowledgeBaseId, + datasetId: input.datasetId, + name: input.name, + }); + + return data?.id; + }), + + removeEvaluation: ragEvalProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input, ctx }) => { + return ctx.evaluationModel.delete(input.id); + }), + + getEvaluationList: ragEvalProcedure + .input(z.object({ knowledgeBaseId: z.string() })) + .query(async ({ ctx, input }) => { + return ctx.evaluationModel.queryByKnowledgeBaseId(input.knowledgeBaseId); + }), +}); diff --git a/src/services/ragEval.ts b/src/services/ragEval.ts new file mode 100644 index 000000000000..9a8477325a72 --- /dev/null +++ b/src/services/ragEval.ts @@ -0,0 +1,67 @@ +import { lambdaClient } from '@/libs/trpc/client'; +import { uploadService } from '@/services/upload'; +import { + CreateNewEvalDatasets, + CreateNewEvalEvaluation, + EvalDatasetRecord, + RAGEvalDataSetItem, + RAGEvalEvaluationItem, + insertEvalDatasetsSchema, +} from '@/types/eval'; + +class RAGEvalService { + // Dataset + async createDataset(params: CreateNewEvalDatasets): Promise<number | undefined> { + return await lambdaClient.ragEval.createDataset.mutate(params); + } + + async getDatasets(knowledgeBaseId: string): Promise<RAGEvalDataSetItem[]> { + return lambdaClient.ragEval.getDatasets.query({ knowledgeBaseId }); + } + + async removeDataset(id: number): Promise<void> { + await lambdaClient.ragEval.removeDataset.mutate({ id }); + } + + async updateDataset(id: number, value: Partial<typeof insertEvalDatasetsSchema>): Promise<void> { + await lambdaClient.ragEval.updateDataset.mutate({ id, value }); + } + + // Dataset Records + async getDatasetRecords(datasetId: number): Promise<EvalDatasetRecord[]> { + return lambdaClient.ragEval.getDatasetRecords.query({ datasetId }); + } + + async removeDatasetRecord(id: number): Promise<void> { + await lambdaClient.ragEval.removeDatasetRecords.mutate({ id }); + } + + async importDatasetRecords(datasetId: number, file: File): Promise<void> { + const { path } = await uploadService.uploadWithProgress(file, { directory: 'ragEval' }); + + await lambdaClient.ragEval.importDatasetRecords.mutate({ datasetId, pathname: path }); + } + + // Evaluation + async createEvaluation(params: CreateNewEvalEvaluation): Promise<number | undefined> { + return await lambdaClient.ragEval.createEvaluation.mutate(params); + } + + async getEvaluationList(knowledgeBaseId: string): Promise<RAGEvalEvaluationItem[]> { + return lambdaClient.ragEval.getEvaluationList.query({ knowledgeBaseId }); + } + + async startEvaluationTask(id: number) { + return lambdaClient.ragEval.startEvaluationTask.mutate({ id }); + } + + async removeEvaluation(id: number): Promise<void> { + await lambdaClient.ragEval.removeEvaluation.mutate({ id }); + } + + async checkEvaluationStatus(id: number): Promise<{ success: boolean }> { + return lambdaClient.ragEval.checkEvaluationStatus.query({ id }); + } +} + +export const ragEvalService = new RAGEvalService(); diff --git a/src/services/upload.ts b/src/services/upload.ts index a4f110627d85..3cd4b9a9f3d8 100644 --- a/src/services/upload.ts +++ b/src/services/upload.ts @@ -11,11 +11,17 @@ export const UPLOAD_NETWORK_ERROR = 'NetWorkError'; class UploadService { uploadWithProgress = async ( file: File, - onProgress: (status: FileUploadStatus, state: FileUploadState) => void, + { + onProgress, + directory, + }: { + directory?: string; + onProgress?: (status: FileUploadStatus, state: FileUploadState) => void; + }, ): Promise<FileMetadata> => { const xhr = new XMLHttpRequest(); - const { preSignUrl, ...result } = await this.getSignedUploadUrl(file); + const { preSignUrl, ...result } = await this.getSignedUploadUrl(file, directory); let startTime = Date.now(); xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { @@ -41,7 +47,7 @@ class UploadService { await new Promise((resolve, reject) => { xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { - onProgress('success', { + onProgress?.('success', { progress: 100, restTime: 0, speed: file.size / ((Date.now() - startTime) / 1000), @@ -95,6 +101,7 @@ class UploadService { private getSignedUploadUrl = async ( file: File, + directory?: string, ): Promise< FileMetadata & { preSignUrl: string; @@ -104,7 +111,7 @@ class UploadService { // 精确到以 h 为单位的 path const date = (Date.now() / 1000 / 60 / 60).toFixed(0); - const dirname = `${fileEnv.NEXT_PUBLIC_S3_FILE_PATH}/${date}`; + const dirname = `${directory || fileEnv.NEXT_PUBLIC_S3_FILE_PATH}/${date}`; const pathname = `${dirname}/${filename}`; const preSignUrl = await edgeClient.upload.createS3PreSignedUrl.mutate({ pathname }); diff --git a/src/store/file/slices/upload/action.ts b/src/store/file/slices/upload/action.ts index 9a5c5c7365b2..c2f781f73170 100644 --- a/src/store/file/slices/upload/action.ts +++ b/src/store/file/slices/upload/action.ts @@ -106,12 +106,14 @@ export const createFileUploadSlice: StateCreator< }); } else { // 2. if file don't exist, need upload files - metadata = await uploadService.uploadWithProgress(file, (status, upload) => { - onStatusUpdate?.({ - id: file.name, - type: 'updateFile', - value: { status: status === 'success' ? 'processing' : status, uploadState: upload }, - }); + metadata = await uploadService.uploadWithProgress(file, { + onProgress: (status, upload) => { + onStatusUpdate?.({ + id: file.name, + type: 'updateFile', + value: { status: status === 'success' ? 'processing' : status, uploadState: upload }, + }); + }, }); } diff --git a/src/store/knowledgeBase/initialState.ts b/src/store/knowledgeBase/initialState.ts index f8599a04390f..75a39c8c4a8b 100644 --- a/src/store/knowledgeBase/initialState.ts +++ b/src/store/knowledgeBase/initialState.ts @@ -1,7 +1,9 @@ import { KnowledgeBaseState, initialKnowledgeBaseState } from '../knowledgeBase/slices/crud'; +import { RAGEvalState, initialDatasetState } from '../knowledgeBase/slices/ragEval'; -export type KnowledgeBaseStoreState = KnowledgeBaseState; +export type KnowledgeBaseStoreState = KnowledgeBaseState & RAGEvalState; export const initialState: KnowledgeBaseStoreState = { ...initialKnowledgeBaseState, + ...initialDatasetState, }; diff --git a/src/store/knowledgeBase/slices/ragEval/actions/dataset.ts b/src/store/knowledgeBase/slices/ragEval/actions/dataset.ts new file mode 100644 index 000000000000..7338e631c803 --- /dev/null +++ b/src/store/knowledgeBase/slices/ragEval/actions/dataset.ts @@ -0,0 +1,88 @@ +import { SWRResponse, mutate } from 'swr'; +import { StateCreator } from 'zustand/vanilla'; + +import { notification } from '@/components/AntdStaticMethods'; +import { useClientDataSWR } from '@/libs/swr'; +import { ragEvalService } from '@/services/ragEval'; +import { KnowledgeBaseStore } from '@/store/knowledgeBase/store'; +import { + CreateNewEvalDatasets, + EvalDatasetRecord, + RAGEvalDataSetItem, + insertEvalDatasetRecordSchema, +} from '@/types/eval'; + +const FETCH_DATASET_LIST_KEY = 'FETCH_DATASET_LIST'; +const FETCH_DATASET_RECORD_KEY = 'FETCH_DATASET_RECORD_KEY'; + +export interface RAGEvalDatasetAction { + createNewDataset: (params: CreateNewEvalDatasets) => Promise<void>; + + importDataset: (file: File, datasetId: number) => Promise<void>; + refreshDatasetList: () => Promise<void>; + removeDataset: (id: number) => Promise<void>; + useFetchDatasetRecords: (datasetId: number | null) => SWRResponse<EvalDatasetRecord[]>; + useFetchDatasets: (knowledgeBaseId: string) => SWRResponse<RAGEvalDataSetItem[]>; +} + +export const createRagEvalDatasetSlice: StateCreator< + KnowledgeBaseStore, + [['zustand/devtools', never]], + [], + RAGEvalDatasetAction +> = (set, get) => ({ + createNewDataset: async (params) => { + await ragEvalService.createDataset(params); + await get().refreshDatasetList(); + }, + + importDataset: async (file, datasetId) => { + if (!datasetId) return; + const fileType = file.name.split('.').pop(); + + if (fileType === 'jsonl') { + // jsonl 文件 需要拆分成单个条,然后逐一校验格式 + const jsonl = await file.text(); + const { default: JSONL } = await import('jsonl-parse-stringify'); + + try { + const items = JSONL.parse(jsonl); + + // check if the items are valid + insertEvalDatasetRecordSchema.array().parse(items); + + // if valid, send to backend + await ragEvalService.importDatasetRecords(datasetId, file); + } catch (e) { + notification.error({ description: (e as Error).message, message: '文件格式错误' }); + } + } + + await get().refreshDatasetList(); + }, + refreshDatasetList: async () => { + await mutate(FETCH_DATASET_LIST_KEY); + }, + + removeDataset: async (id) => { + await ragEvalService.removeDataset(id); + await get().refreshDatasetList(); + }, + useFetchDatasetRecords: (datasetId) => + useClientDataSWR<EvalDatasetRecord[]>( + !!datasetId ? [FETCH_DATASET_RECORD_KEY, datasetId] : null, + () => ragEvalService.getDatasetRecords(datasetId!), + ), + useFetchDatasets: (knowledgeBaseId) => + useClientDataSWR<RAGEvalDataSetItem[]>( + [FETCH_DATASET_LIST_KEY, knowledgeBaseId], + () => ragEvalService.getDatasets(knowledgeBaseId), + { + fallbackData: [], + onSuccess: () => { + if (!get().initDatasetList) + set({ initDatasetList: true }, false, 'useFetchDatasets/init'); + }, + }, + ), +}); diff --git a/src/store/knowledgeBase/slices/ragEval/actions/evaluation.ts b/src/store/knowledgeBase/slices/ragEval/actions/evaluation.ts new file mode 100644 index 000000000000..f14ef577d528 --- /dev/null +++ b/src/store/knowledgeBase/slices/ragEval/actions/evaluation.ts @@ -0,0 +1,62 @@ +import { SWRResponse, mutate } from 'swr'; +import { StateCreator } from 'zustand/vanilla'; + +import { useClientDataSWR } from '@/libs/swr'; +import { ragEvalService } from '@/services/ragEval'; +import { KnowledgeBaseStore } from '@/store/knowledgeBase/store'; +import { CreateNewEvalEvaluation, RAGEvalDataSetItem } from '@/types/eval'; + +const FETCH_EVALUATION_LIST_KEY = 'FETCH_EVALUATION_LIST_KEY'; + +export interface RAGEvalEvaluationAction { + checkEvaluationStatus: (id: number) => Promise<void>; + + createNewEvaluation: (params: CreateNewEvalEvaluation) => Promise<void>; + refreshEvaluationList: () => Promise<void>; + + removeEvaluation: (id: number) => Promise<void>; + runEvaluation: (id: number) => Promise<void>; + + useFetchEvaluationList: (knowledgeBaseId: string) => SWRResponse<RAGEvalDataSetItem[]>; +} + +export const createRagEvalEvaluationSlice: StateCreator< + KnowledgeBaseStore, + [['zustand/devtools', never]], + [], + RAGEvalEvaluationAction +> = (set, get) => ({ + checkEvaluationStatus: async (id) => { + await ragEvalService.checkEvaluationStatus(id); + }, + + createNewEvaluation: async (params) => { + await ragEvalService.createEvaluation(params); + await get().refreshEvaluationList(); + }, + refreshEvaluationList: async () => { + await mutate(FETCH_EVALUATION_LIST_KEY); + }, + + removeEvaluation: async (id) => { + await ragEvalService.removeEvaluation(id); + // await get().refreshEvaluationList(); + }, + + runEvaluation: async (id) => { + await ragEvalService.startEvaluationTask(id); + }, + + useFetchEvaluationList: (knowledgeBaseId) => + useClientDataSWR<RAGEvalDataSetItem[]>( + [FETCH_EVALUATION_LIST_KEY, knowledgeBaseId], + () => ragEvalService.getEvaluationList(knowledgeBaseId), + { + fallbackData: [], + onSuccess: () => { + if (!get().initDatasetList) + set({ initDatasetList: true }, false, 'useFetchDatasets/init'); + }, + }, + ), +}); diff --git a/src/store/knowledgeBase/slices/ragEval/actions/index.ts b/src/store/knowledgeBase/slices/ragEval/actions/index.ts new file mode 100644 index 000000000000..d50a0d19b5c0 --- /dev/null +++ b/src/store/knowledgeBase/slices/ragEval/actions/index.ts @@ -0,0 +1,20 @@ +import { StateCreator } from 'zustand/vanilla'; + +import { KnowledgeBaseStore } from '@/store/knowledgeBase/store'; + +import { RAGEvalDatasetAction, createRagEvalDatasetSlice } from './dataset'; +import { RAGEvalEvaluationAction, createRagEvalEvaluationSlice } from './evaluation'; + +export interface RAGEvalAction extends RAGEvalDatasetAction, RAGEvalEvaluationAction { + // empty +} + +export const createRagEvalSlice: StateCreator< + KnowledgeBaseStore, + [['zustand/devtools', never]], + [], + RAGEvalAction +> = (...params) => ({ + ...createRagEvalDatasetSlice(...params), + ...createRagEvalEvaluationSlice(...params), +}); diff --git a/src/store/knowledgeBase/slices/ragEval/index.ts b/src/store/knowledgeBase/slices/ragEval/index.ts new file mode 100644 index 000000000000..1b2275e77be7 --- /dev/null +++ b/src/store/knowledgeBase/slices/ragEval/index.ts @@ -0,0 +1,2 @@ +export * from './actions'; +export * from './initialState'; diff --git a/src/store/knowledgeBase/slices/ragEval/initialState.ts b/src/store/knowledgeBase/slices/ragEval/initialState.ts new file mode 100644 index 000000000000..71fd5e17ad01 --- /dev/null +++ b/src/store/knowledgeBase/slices/ragEval/initialState.ts @@ -0,0 +1,7 @@ +export interface RAGEvalState { + initDatasetList: boolean; +} + +export const initialDatasetState: RAGEvalState = { + initDatasetList: false, +}; diff --git a/src/store/knowledgeBase/store.ts b/src/store/knowledgeBase/store.ts index a59d6ef54dfe..ae293d2909f1 100644 --- a/src/store/knowledgeBase/store.ts +++ b/src/store/knowledgeBase/store.ts @@ -6,12 +6,17 @@ import { createDevtools } from '../middleware/createDevtools'; import { KnowledgeBaseStoreState, initialState } from './initialState'; import { KnowledgeBaseContentAction, createContentSlice } from './slices/content'; import { KnowledgeBaseCrudAction, createCrudSlice } from './slices/crud'; +import { RAGEvalAction, createRagEvalSlice } from './slices/ragEval'; // =============== 聚合 createStoreFn ============ // -export type KnowledgeBaseStore = KnowledgeBaseStoreState & - KnowledgeBaseCrudAction & - KnowledgeBaseContentAction; +export interface KnowledgeBaseStore + extends KnowledgeBaseStoreState, + KnowledgeBaseCrudAction, + KnowledgeBaseContentAction, + RAGEvalAction { + // empty +} const createStore: StateCreator<KnowledgeBaseStore, [['zustand/devtools', never]]> = ( ...parameters @@ -19,6 +24,7 @@ const createStore: StateCreator<KnowledgeBaseStore, [['zustand/devtools', never] ...initialState, ...createCrudSlice(...parameters), ...createContentSlice(...parameters), + ...createRagEvalSlice(...parameters), }); // =============== 实装 useStore ============ // diff --git a/src/store/serverConfig/selectors.test.ts b/src/store/serverConfig/selectors.test.ts index 15862de31f35..fcd533527973 100644 --- a/src/store/serverConfig/selectors.test.ts +++ b/src/store/serverConfig/selectors.test.ts @@ -20,6 +20,7 @@ describe('featureFlagsSelectors', () => { enableWebrtc: false, isAgentEditable: false, showCreateSession: true, + enableRAGEval: false, showDalle: true, enableKnowledgeBase: true, showLLM: false, diff --git a/src/types/eval/dataset.ts b/src/types/eval/dataset.ts new file mode 100644 index 000000000000..5378001ea85e --- /dev/null +++ b/src/types/eval/dataset.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; + +export interface EvalDatasetRecordRefFile { + fileType: string; + id: string; + name: string; +} +export interface EvalDatasetRecord { + createdAt: Date; + id: number; + ideal?: string | null; + + metadata: any; + + question?: string | null; + + /** + * The reference files for the question + */ + referenceFiles?: EvalDatasetRecordRefFile[] | null; +} + +export const insertEvalDatasetRecordSchema = z.object({ + ideal: z.string().optional(), + + question: z.string(), + + referenceFiles: z.string().or(z.array(z.string())).optional(), +}); + +export type InsertEvalDatasetRecord = z.infer<typeof insertEvalDatasetRecordSchema>; + +export interface RAGEvalDataSetItem { + createdAt: Date; + description?: string | null; + id: number; + name: string; + updatedAt: Date; +} + +export const insertEvalDatasetsSchema = z.object({ + description: z.string().optional(), + knowledgeBaseId: z.string(), + name: z.string(), +}); + +export type CreateNewEvalDatasets = z.infer<typeof insertEvalDatasetsSchema>; diff --git a/src/types/eval/evaluation.ts b/src/types/eval/evaluation.ts new file mode 100644 index 000000000000..eb14dd7fe82f --- /dev/null +++ b/src/types/eval/evaluation.ts @@ -0,0 +1,53 @@ +import { z } from 'zod'; + +export enum EvalEvaluationStatus { + Error = 'Error', + Pending = 'Pending', + Processing = 'Processing', + Success = 'Success', +} + +export interface EvaluationRecord { + answer: string; + context: string[]; + createdAt: Date; + id: number; + ideal: string; + question: string; +} + +export const insertEvaluationSchema = z.object({ + ideal: z.string().optional(), + + question: z.string(), + + referenceFiles: z.string().or(z.array(z.string())).optional(), +}); + +export type InsertEvaluationRecord = z.infer<typeof insertEvaluationSchema>; + +export interface RAGEvalEvaluationItem { + createdAt: Date; + dataset: { + id: number; + name: string; + }; + evalRecordsUrl?: string; + id: number; + name: string; + recordsStats: { + success: number; + total: number; + }; + status: EvalEvaluationStatus; + updatedAt: Date; +} + +export const insertEvalEvaluationSchema = z.object({ + datasetId: z.number(), + description: z.string().optional(), + knowledgeBaseId: z.string(), + name: z.string(), +}); + +export type CreateNewEvalEvaluation = z.infer<typeof insertEvalEvaluationSchema>; diff --git a/src/types/eval/index.ts b/src/types/eval/index.ts new file mode 100644 index 000000000000..7cdbc1b7a615 --- /dev/null +++ b/src/types/eval/index.ts @@ -0,0 +1,3 @@ +export * from './dataset'; +export * from './evaluation'; +export * from './ragas'; diff --git a/src/types/eval/ragas.ts b/src/types/eval/ragas.ts new file mode 100644 index 000000000000..24f87edc7933 --- /dev/null +++ b/src/types/eval/ragas.ts @@ -0,0 +1,9 @@ +/** + * The data set for the RAGAS benchmark + */ +export interface RAGASDataSetItem { + answer: string; + context: string; + ground_truth: string; + question: string; +}