Remove fine-tuning flow ()

remove fine-tuning flow
This commit is contained in:
Timothy Carambat 2024-12-18 10:24:02 -08:00 committed by GitHub
parent b082c8e441
commit d54b5dfc62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 8 additions and 2117 deletions
frontend/src
App.jsx
models/experimental
pages
FineTuning
Steps
Confirmation
DataUpload
FulfillmentPolicy
Introduction
OrderDetails
OrderPlaced
Privacy
TermsAndConditions
index.jsx
index.jsx
GeneralSettings/Chats
utils
server
endpoints/experimental
models

View file

@ -67,7 +67,6 @@ const ExperimentalFeatures = lazy(
const LiveDocumentSyncManage = lazy(
() => import("@/pages/Admin/ExperimentalFeatures/Features/LiveSync/manage")
);
const FineTuningWalkthrough = lazy(() => import("@/pages/FineTuning"));
const CommunityHubTrending = lazy(
() => import("@/pages/GeneralSettings/CommunityHub/Trending")
@ -213,11 +212,6 @@ export default function App() {
element={<AdminRoute Component={LiveDocumentSyncManage} />}
/>
<Route
path="/fine-tuning"
element={<AdminRoute Component={FineTuningWalkthrough} />}
/>
<Route
path="/settings/community-hub/trending"
element={<AdminRoute Component={CommunityHubTrending} />}

View file

@ -1,129 +0,0 @@
import { API_BASE } from "@/utils/constants";
import { baseHeaders, safeJsonParse } from "@/utils/request";
const FineTuning = {
cacheKeys: {
dismissed_cta: "anythingllm_dismissed_fine_tune_notif",
eligibility: "anythingllm_can_fine_tune",
},
/**
* Get the information for the Fine-tuning product to display in various frontends
* @returns {Promise<{
* productDetails: {
* name: string,
* description: string,
* icon: string,
* active: boolean,
* },
* pricing: {
* usd: number,
* },
* availableBaseModels: string[]
* }>}
*/
info: async function () {
return await fetch(`${API_BASE}/experimental/fine-tuning/info`, {
method: "GET",
headers: baseHeaders(),
})
.then((res) => {
if (!res.ok) throw new Error("Could not get model info.");
return res.json();
})
.then((res) => res)
.catch((e) => {
console.error(e);
return null;
});
},
datasetStat: async function ({ slugs = [], feedback = null }) {
return await fetch(`${API_BASE}/experimental/fine-tuning/dataset`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({ slugs, feedback }),
})
.then((res) => {
if (!res.ok) throw new Error("Could not get dataset info.");
return res.json();
})
.then((res) => res)
.catch((e) => {
console.error(e);
return { count: null };
});
},
/**
* Generates Fine-Tuning order.
* @param {{email:string, baseModel:string, modelName: string, trainingData: {slugs:string[], feedback:boolean|null}}} param0
* @returns {Promise<{checkoutUrl:string, jobId:string}|null>}
*/
createOrder: async function ({ email, baseModel, modelName, trainingData }) {
return await fetch(`${API_BASE}/experimental/fine-tuning/order`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({
email,
baseModel,
modelName,
trainingData,
}),
})
.then((res) => {
if (!res.ok) throw new Error("Could not order fine-tune.");
return res.json();
})
.then((res) => res)
.catch((e) => {
console.error(e);
return null;
});
},
/**
* Determine if a user should see the CTA alert. In general this alert
* Can only render if the user is empty (single user) or is an admin role.
* @returns {boolean}
*/
canAlert: function (user = null) {
if (!!user && user.role !== "admin") return false;
return !window?.localStorage?.getItem(this.cacheKeys.dismissed_cta);
},
checkEligibility: async function () {
const cache = window.localStorage.getItem(this.cacheKeys.eligibility);
if (!!cache) {
const { data, lastFetched } = safeJsonParse(cache, {
data: null,
lastFetched: 0,
});
if (!!data && Date.now() - lastFetched < 1.8e7)
// 5 hours
return data.eligible;
}
return await fetch(`${API_BASE}/experimental/fine-tuning/check-eligible`, {
method: "GET",
headers: baseHeaders(),
})
.then((res) => {
if (!res.ok) throw new Error("Could not check if eligible.");
return res.json();
})
.then((res) => {
window.localStorage.setItem(
this.cacheKeys.eligibility,
JSON.stringify({
data: { eligible: res.eligible },
lastFetched: Date.now(),
})
);
return res.eligible;
})
.catch((e) => {
console.error(e);
return false;
});
},
};
export default FineTuning;

View file

@ -1,198 +0,0 @@
import FineTuning from "@/models/experimental/fineTuning";
import { dollarFormat } from "@/utils/numbers";
import showToast from "@/utils/toast";
import { Check } from "@phosphor-icons/react";
import { useState, useEffect } from "react";
import FineTuningSteps from "../index";
import CTAButton from "@/components/lib/CTAButton";
import Workspace from "@/models/workspace";
/**
* @param {{settings: import("../index").OrderSettings}} param0
* @returns
*/
export default function Confirmation({ settings, setSettings, setStep }) {
const [loading, setLoading] = useState(false);
const [workspaces, setWorkspaces] = useState([]);
useEffect(() => {
Workspace.all()
.then((fetchedWorkspaces) => {
setWorkspaces(fetchedWorkspaces);
})
.catch(() => {
showToast("Failed to fetch workspaces", "error");
});
}, []);
async function handleCheckout() {
setLoading(true);
const data = await FineTuning.createOrder({
email: settings.email,
baseModel: settings.baseModel,
modelName: settings.modelName,
trainingData: {
slugs: settings.trainingData.slugs,
feedback: settings.trainingData.feedback,
},
});
if (!data) {
setLoading(false);
showToast("Could not generate new order.", "error", { clear: true });
return;
}
window.open(data.checkoutUrl, "_blank");
setSettings((prev) => {
return {
...prev,
jobId: data.jobId,
checkoutUrl: data.checkoutUrl,
};
});
setStep(FineTuningSteps.confirmation.next());
}
const getWorkspaceName = (slug) => {
const workspace = workspaces.find((ws) => ws.slug === slug);
return workspace ? workspace.name : slug;
};
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
Confirm & Submit
</h2>
<p className="text-theme-text-secondary text-sm">
Below are your fine-tuning order details. If you have any questions
before or after ordering your fine-tune you can checkout the{" "}
<a
href="https://docs.anythingllm.com/fine-tuning/overview"
target="_blank"
rel="noreferrer"
className="underline text-sky-400"
>
fine-tuning FAQ
</a>{" "}
or email{" "}
<a
className="underline text-sky-400"
href="mailto:team@mintplexlabs.com"
>
team@mintplexlabs.com
</a>
.
</p>
<div className="p-4 bg-theme-bg-container text-theme-text-primary flex flex-col gap-y-2 rounded-lg mt-4">
<div className="flex flex-col gap-y-3 text-sm">
<div className="flex items-start gap-x-1">
<p className="w-1/3">Contact e-mail:</p>
<p className="text-theme-text-secondary w-2/3">
{settings.email}
</p>
</div>
<div className="flex items-start gap-x-1">
<p className="w-1/3">Base LLM:</p>
<p className="text-theme-text-secondary w-2/3">
{settings.baseModel}
</p>
</div>
<div className="flex items-start gap-x-1">
<p className="w-1/3">Output model name:</p>
<p className="text-theme-text-secondary w-2/3">
"{settings.modelName}"
</p>
</div>
<div className="flex items-start gap-x-1">
<p className="w-1/3">Training on workspaces:</p>
<div className="text-theme-text-secondary w-2/3 flex flex-wrap gap-1">
{settings.trainingData.slugs.map((slug) => (
<span
key={slug}
className="rounded-full bg-theme-bg-secondary px-2 py-0.5 h-[20px] text-xs font-medium text-theme-text-primary shadow-sm"
>
{getWorkspaceName(slug)}
</span>
))}
</div>
</div>
<div className="flex items-start gap-x-1">
<p className="w-1/3">Training data:</p>
<p className="text-theme-text-secondary w-2/3">
{settings.trainingData.feedback === true
? "Training on positive-feedback chats only"
: "Training on all chats"}
</p>
</div>
</div>
<div className="mt-4">
<ul className="flex flex-col gap-y-1 text-sm">
<li className="flex items-center gap-x-2">
<Check
className="text-theme-text-primary"
size={12}
weight="bold"
/>
<p className="text-theme-text-secondary">
Agreed to Terms and Conditions
</p>
</li>
<li className="flex items-center gap-x-2">
<Check
className="text-theme-text-primary"
size={12}
weight="bold"
/>
<p className="text-theme-text-secondary">
Understand privacy & data handling
</p>
</li>
<li className="flex items-center gap-x-2">
<Check
className="text-theme-text-primary"
size={12}
weight="bold"
/>
<p className="text-theme-text-secondary">
Agreed to Fulfillment terms
</p>
</li>
</ul>
</div>
<div className="mt-4 border-theme-border pt-2">
<div className="flex items-center gap-x-1 text-lg mb-0">
<p className="text-theme-text-primary">Total one-time cost:</p>
<p className="text-theme-text-secondary">
{dollarFormat(settings.tuningInfo.pricing.usd)}
<sup>*</sup>
</p>
</div>
<p className="m-0 p-0 text-xs text-theme-text-tertiary">
<sup>*</sup> price does not include any coupons, incentives, or
discounts you can apply at checkout.
</p>
</div>
</div>
<p className="text-xs text-theme-text-secondary mt-4">
Once you proceed to checkout, if you do not complete this purchase
your data will be deleted from our servers within 1 hour of
abandonment of the creation of the checkout in accordance to our
privacy and data handling policy.
</p>
<CTAButton
disabled={loading}
onClick={handleCheckout}
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
>
{loading ? "Generating order..." : "Start Training →"}
</CTAButton>
</div>
</div>
</div>
);
}

View file

@ -1,290 +0,0 @@
import { useEffect, useState } from "react";
import FineTuning from "@/models/experimental/fineTuning";
import Workspace from "@/models/workspace";
import {
CheckCircle,
Warning,
X,
MagnifyingGlass,
} from "@phosphor-icons/react";
import FineTuningSteps from "..";
import CTAButton from "@/components/lib/CTAButton";
export default function DataUpload({ setSettings, setStep }) {
const [workspaces, setWorkspaces] = useState([]);
const [dataFilters, setDataFilters] = useState({
workspaces: [],
feedback: null,
});
useEffect(() => {
Workspace.all()
.then((workspaces) => {
const workspaceOpts = workspaces.map((ws) => {
return { slug: ws.slug, name: ws.name };
});
setWorkspaces(workspaceOpts);
setDataFilters((prev) => {
return { ...prev, workspaces: workspaceOpts };
});
})
.catch(() => null);
}, []);
async function handleSubmit(e) {
e.preventDefault();
setSettings((prev) => {
return {
...prev,
trainingData: {
slugs: dataFilters.workspaces.map((ws) => ws.slug),
feedback: dataFilters.feedback,
},
};
});
setStep(FineTuningSteps["data-selection"].next());
}
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
Select your training dataset
</h2>
<p className="text-theme-text-secondary text-sm">
This is the data your model will be trained and tuned on. This is a
critical step and you should always train on the exact information
you want the model to inherit. By default, AnythingLLM will use all
chats, but you can filter chats by workspace and even limit training
to chats which users have left a positive feedback indication on
(thumbs up).
</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-y-6 mt-4">
<div className="flex flex-col">
<label className="text-theme-text-primary text-sm font-semibold block mb-3">
Only use positive responses
</label>
<p className="text-xs font-normal text-theme-text-secondary mb-2">
Enabling this toggle will filter your dataset to only use
"positive" responses that were marked during chatting.
</p>
<label className="relative inline-flex cursor-pointer items-center w-fit">
<input
type="checkbox"
onClick={() =>
setDataFilters((prev) => {
return { ...prev, feedback: !prev.feedback };
})
}
checked={dataFilters.feedback}
className="peer sr-only pointer-events-none"
/>
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
</label>
</div>
<div className="flex flex-col">
<label className="text-theme-text-primary text-sm font-semibold block mb-3">
Selected Workspaces
</label>
<p className="text-xs font-normal text-theme-text-secondary mb-2">
Your training data will be limited to these workspaces.
</p>
<WorkspaceSelector
workspaces={workspaces}
selectedWorkspaces={dataFilters.workspaces}
setDataFilters={setDataFilters}
/>
</div>
<DatasetSummary
workspaces={dataFilters.workspaces}
feedback={dataFilters.feedback}
/>
<CTAButton
type="submit"
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
>
Proceed to Confirmation &rarr;
</CTAButton>
</form>
</div>
</div>
</div>
);
}
function WorkspaceSelector({
workspaces = [],
selectedWorkspaces = [],
setDataFilters,
}) {
const [query, setQuery] = useState("");
const [showSuggestions, setShowSuggestions] = useState(false);
const availableWorkspaces = workspaces.filter(
(ws) =>
!selectedWorkspaces.find((selectedWs) => selectedWs.slug === ws.slug)
);
function handleAddWorkspace(workspace) {
setDataFilters((prev) => {
return {
...prev,
workspaces: [...prev.workspaces, workspace],
};
});
setQuery("");
setShowSuggestions(false);
}
function handleRemoveWorkspace(workspace) {
setDataFilters((prev) => {
const filtered = prev.workspaces.filter(
(ws) => ws.slug !== workspace.slug
);
return {
...prev,
workspaces: filtered,
};
});
setQuery("");
setShowSuggestions(false);
}
return (
<div className="flex flex-col gap-y-2">
<div className="min-w-[150px] max-w-[300px] h-[32px] p-[10px] rounded-lg flex items-center bg-theme-settings-input-bg mt-1">
<MagnifyingGlass size={16} className="text-theme-text-primary" />
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => setShowSuggestions(true)}
onBlur={() =>
setTimeout(() => {
setShowSuggestions(false);
}, 500)
}
placeholder="Enter a workspace name"
className="border-none bg-transparent p-1 px-2 appearance-none outline-none h-full w-full text-theme-text-primary text-xs placeholder:text-theme-text-secondary/50"
/>
</div>
<div className="flex flex-col items-center -ml-2">
<div className="w-full h-fit">
<div className="w-full relative z-1">
<div className="p-2 flex rounded-lg">
<div className="flex flex-wrap gap-2 w-full">
{selectedWorkspaces.map((workspace) => {
return (
<div
key={workspace.slug}
className="flex items-center justify-between rounded-full h-[20px] bg-theme-bg-container px-2 py-1 text-xs font-medium text-theme-text-primary shadow-sm light:border light:bg-theme-bg-secondary"
>
<span className="truncate mr-1">{workspace.name}</span>
<button
onClick={() => handleRemoveWorkspace(workspace)}
type="button"
className="hover:text-red-500 flex-shrink-0"
>
<X size={10} weight="bold" />
</button>
</div>
);
})}
</div>
</div>
</div>
{showSuggestions && (
<div className="w-full flex relative">
<div className="w-full absolute top-0 z-20">
<WorkspaceSuggestions
availableWorkspaces={availableWorkspaces}
addWorkspace={handleAddWorkspace}
query={query}
/>
</div>
</div>
)}
</div>
</div>
</div>
);
}
function WorkspaceSuggestions({
availableWorkspaces = [],
addWorkspace,
query = "",
}) {
if (availableWorkspaces.length === 0) {
return (
<div className="w-full mt-[2px] bg-theme-bg-container top-[45px] h-40 rounded-lg p-2 text-sm">
<p className="text-center text-theme-text-secondary">
no workspaces available to select.
</p>
</div>
);
}
const filteredWorkspace = !!query
? availableWorkspaces.filter((ws) => {
return (
ws.slug.toLowerCase().includes(query.toLowerCase()) ||
ws.name.toLowerCase().includes(query.toLowerCase())
);
})
: availableWorkspaces;
return (
<div className="w-full mt-[2px] bg-theme-bg-container top-[45px] h-40 rounded-lg p-2 text-sm flex flex-col gap-y-1 justify-start overflow-y-scroll">
{filteredWorkspace.map((workspace) => {
return (
<button
key={workspace.slug}
onClick={() => addWorkspace(workspace)}
type="button"
className="border-none text-left text-theme-text-primary hover:bg-theme-bg-secondary rounded-lg p-1"
>
{workspace.name}
</button>
);
})}
</div>
);
}
function DatasetSummary({ workspaces = [], feedback = null }) {
const [stats, setStats] = useState({ count: null, recommendedMin: 50 });
useEffect(() => {
function getStats() {
const slugs = workspaces?.map((ws) => ws.slug);
if (!slugs || slugs.length === 0) return;
FineTuning.datasetStat({ slugs, feedback })
.then((stats) => setStats(stats))
.catch((e) => null);
}
getStats();
}, [workspaces, feedback]);
return (
<div className="bg-theme-bg-container text-theme-text-secondary p-4 rounded-lg text-sm">
<p>Training dataset size: {stats.count ?? "Unknown"}</p>
{stats.count < stats.recommendedMin ? (
<div className="flex items-center gap-x-1 text-red-500 text-sm p-2 rounded-lg bg-red-500/20 w-fit my-2">
<Warning size={12} weight="bold" />
<p>
Dataset size is below recommended minimum ({stats.recommendedMin})
</p>
</div>
) : (
<div className="flex items-center gap-x-1 text-green-500 text-sm p-2 rounded-lg bg-green-500/20 w-fit my-2">
<CheckCircle size={12} weight="bold" />
<p>Dataset size is sufficient</p>
</div>
)}
</div>
);
}

View file

@ -1,131 +0,0 @@
import CTAButton from "@/components/lib/CTAButton";
import FineTuningSteps from "..";
export default function Fulfillment({ setSettings, setStep }) {
const handleAccept = () => {
setSettings((prev) => {
return { ...prev, agreedToTerms: true };
});
setStep(FineTuningSteps.fulfillment.next());
};
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
Fulfillment Policy
</h2>
<p className="text-theme-text-secondary text-sm">
Fulfillment of a fine-tune model is straight-forward. We do not host
your model. We provide you a download link to run the model in a
standard format where ever you run local LLMs
</p>
<div className="flex flex-col gap-y-2 text-theme-text-secondary text-xs font-semibold rounded-lg p-4 h-[60vh] overflow-y-auto bg-theme-bg-container mt-2">
<div className="text-xs">
<h1 className="text-theme-text-primary">Fulfillment Terms</h1>
<p className="text-theme-text-secondary">
Last updated: July 15, 2024
</p>
</div>
<p className="text-theme-text-secondary">
These fulfillment terms outline the agreement between Mintplex
Labs Inc. ("Company," "we," "us," or "our") and the customer
regarding the creation and delivery of fine-tuned models.
</p>
<h2 className="text-theme-text-primary mt-4">Delivery of Model</h2>
<p className="text-theme-text-secondary">
Upon completion of a fine-tuning job, we will deliver a download
link to a .gguf model file suitable for LLM text inferencing. The
customer acknowledges that this exchange is strictly transactional
and non-recurring. Once the model file is delivered, the agreement
is considered concluded and will be ineligible for a refund.
</p>
<h2 className="text-theme-text-primary mt-4">Support</h2>
<p className="text-theme-text-secondary">
Please note that the delivery of the model does not include any
dedicated support. Customers are encouraged to refer to available
documentation and resources for guidance on using the model.
</p>
<h2 className="text-theme-text-primary mt-4">
Requesting Download Links
</h2>
<p className="text-theme-text-secondary">
Customers may request refreshed download links from
my.mintplexlabs.com as long as the model is retained in our cloud
storage. We will retain a model in our storage for a maximum of 3
months or until the customer requests its removal. All download
links are valid for 24 hours.
</p>
<h2 className="text-theme-text-primary mt-4">
Cancellation and Refunds
</h2>
<p className="text-theme-text-secondary">
Mintplex Labs Inc. reserves the right to cancel any fine-tuning
job at our discretion. In the event of a cancellation, a refund
may be issued. Additionally, we reserve the right to deny a
payment from the Customer or issue refunds for any reason without
cause or notice to the Customer.
</p>
<h2 className="text-theme-text-primary mt-4">No Guarantees</h2>
<p className="text-theme-text-secondary">
Mintplex Labs Inc. makes <strong>NO GUARANTEES</strong> regarding
the resulting model's output, functionality, speed, or
compatibility with your tools, infrastructure and devices. Refund
requests of this nature are not eligible for refunds.
</p>
<p className="text-theme-text-secondary">
Models are delivered and accepted in "As-Is" condition. All
delivered model and output files are deemed final and
non-refundable for any reason after training is complete and a
model has been generated.
</p>
<h2 className="text-theme-text-primary mt-4">Payment Terms</h2>
<p className="text-theme-text-secondary">
All payments are required prior to the commencement of the
fine-tuning process. Customers are responsible for ensuring that
valid payment information is provided. Checkout sessions not
completed within 1 hour of creation will be considered as
abandoned and will be deleted from our system.
</p>
<h2 className="text-theme-text-primary">
Denial of Service for Payment Reasons
</h2>
<p className="text-theme-text-secondary">
Mintplex Labs Inc. reserves the right to deny service to any
customer with an outstanding balance or invalid payment
information. If any discrepancies arise regarding payment or
usage, we may suspend services until the matter is resolved.
</p>
<h2 className="text-theme-text-primary">Contact</h2>
<p className="text-theme-text-secondary">
For any questions related to payment or fulfillment of services,
please contact us at{" "}
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
.
</p>
</div>
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
>
Agree and continue &rarr;
</CTAButton>
</div>
</div>
</div>
);
}

View file

@ -1,136 +0,0 @@
import { Check, X } from "@phosphor-icons/react";
import FineTuningSteps from "..";
import CTAButton from "@/components/lib/CTAButton";
export default function Introduction({ setSettings, setStep }) {
const handleAccept = () => {
setSettings((prev) => {
return { ...prev, agreedToTerms: true };
});
setStep(FineTuningSteps.intro.next());
};
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-2 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
What is a "Fine-Tuned" model?
</h2>
<div className="flex flex-col gap-y-[25px] text-theme-text-secondary text-sm">
<p>
Fine-tuned models are basically "customized"
Language-Learning-Models (LLMs). These can be based on popular
open-source <b>foundational</b> models like LLama3 8B or even some
closed source models like GPT-3.5.
</p>
<p>
Typically, you would use an open-source model - you probably are
using one right now with AnythingLLM!
</p>
<p>
When you create a custom fine-tune with AnythingLLM we will train
a custom base model on your specific data already inside of this
AnythingLLM instance and give you back a <code>GGUF</code> file
you can then load back into tools like Ollama, LMStudio, and
anywhere else you use local LLMs.
</p>
</div>
<div className="flex flex-col gap-y-2 text-theme-text-secondary text-sm mt-4">
<h3 className="text-base text-theme-text-primary font-semibold">
When should I get a fine-tuned model?
</h3>
<p>
Fine-tuned models are perfect for when you need any of the
following
</p>
<ul className="flex flex-col gap-y-1">
<li className="flex items-center gap-x-2">
<Check
className="text-theme-text-primary"
size={12}
weight="bold"
/>{" "}
Setting the style, tone, format, or other qualitative aspects
without prompting
</li>
<li className="flex items-center gap-x-2">
<Check
className="text-theme-text-primary"
size={12}
weight="bold"
/>{" "}
Improving reliability at producing a desired output
</li>
<li className="flex items-center gap-x-2">
<Check
className="text-theme-text-primary"
size={12}
weight="bold"
/>{" "}
Correcting failures to follow complex prompts, citations, or
lack of background knowledge
</li>
<li className="flex items-center gap-x-2">
<Check
className="text-theme-text-primary"
size={12}
weight="bold"
/>{" "}
You want to run this model privately or offline
</li>
</ul>
</div>
<div className="flex flex-col gap-y-2 text-theme-text-secondary text-sm mt-4">
<h3 className="text-base text-theme-text-primary font-semibold">
What are fine-tunes bad for?
</h3>
<p>
Fine-tuned models are powerful, but they are not the "silver
bullet" to any issues you have with RAG currently. Some notable
limitations are
</p>
<ul>
<li className="flex items-center gap-x-1">
<X
className="text-theme-text-primary"
size={12}
weight="bold"
/>{" "}
You need perfect recall of some piece of literature or reference
document
</li>
<li className="flex items-center gap-x-1">
<X
className="text-theme-text-primary"
size={12}
weight="bold"
/>{" "}
You want your model to have perfect memory or recollection
</li>
</ul>
</div>
<div className="flex flex-col gap-y-2 text-theme-text-secondary text-sm">
<p>
In summary, if you are getting good results with RAG currently,
creating a fine-tune can squeeze <b>even more performance</b> out
of a model. Fine-Tunes are for improving response quality and
general responses, but they are <b>not for knowledge recall</b> -
that is what RAG is for! Together, it is a powerful combination.
</p>
</div>
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
text="Create fine-tune model &rarr;"
>
Create a fine-tune model &rarr;
</CTAButton>
</div>
</div>
</div>
);
}

View file

@ -1,132 +0,0 @@
import FineTuning from "@/models/experimental/fineTuning";
import { useEffect, useState } from "react";
import FineTuningSteps from "..";
import { CircleNotch } from "@phosphor-icons/react";
import CTAButton from "@/components/lib/CTAButton";
export default function OrderDetails({ setSettings, setStep }) {
const [info, setInfo] = useState({});
useEffect(() => {
FineTuning.info()
.then((res) => {
setInfo(res ?? {});
setSettings((prev) => {
return { ...prev, tuningInfo: res };
});
})
.catch(() => setInfo({}));
}, []);
async function handleSubmit(e) {
e.preventDefault();
const form = new FormData(e.target);
setSettings((prev) => {
return {
...prev,
email: form.get("email"),
baseModel: form.get("baseModel"),
modelName: form.get("modelName"),
};
});
setStep(FineTuningSteps["order-details"].next());
}
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
Time to create your fine tune!
</h2>
<p className="text-theme-text-secondary text-sm">
Creating a model is quite simple. Currently we have a limited base
model selection, however in the future we plan to expand support to
many more foundational models.
</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-y-6 mt-4">
<div className="flex flex-col">
<label className="text-theme-text-primary text-sm font-semibold block mb-3">
Account e-mail
</label>
<p className="text-xs font-normal text-theme-text-secondary mb-2">
This e-mail is where you will receive all order information and
updates. This e-mail <b>must be accurate</b> or else we won't be
able to contact you with your fine-tuned model!
</p>
<input
type="email"
name="email"
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="jdoe@example.com"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col">
<label className="text-theme-text-primary text-sm font-semibold block mb-3">
Preferred Base Model
</label>
<p className="text-xs font-normal text-theme-text-secondary mb-2">
This is the foundational model your fine-tune will be based on.
We recommend Llama 3 8B.
</p>
{info.hasOwnProperty("availableBaseModels") ? (
<select
name="baseModel"
required={true}
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
>
<option disabled value="">
-- select a base model --
</option>
<optgroup label="Available base models">
{(info?.availableBaseModels || []).map((model) => (
<option key={model} value={model}>
{model}
</option>
))}
</optgroup>
</select>
) : (
<div className="flex items-center gap-x-2 text-theme-text-secondary text-sm">
<CircleNotch className="animate-spin" size={12} />
<p>fetching available models...</p>
</div>
)}
</div>
<div className="flex flex-col">
<label className="text-theme-text-primary text-sm font-semibold block mb-3">
Model name
</label>
<p className="text-xs font-normal text-theme-text-secondary mb-2">
What would you like to call your model? This has no impact on
its output or training and is only used for how we communicate
with you about the model.
</p>
<input
type="text"
name="modelName"
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="My really cool model!"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<CTAButton
type="submit"
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
>
Proceed to data selection &rarr;
</CTAButton>
</form>
</div>
</div>
</div>
);
}

View file

@ -1,83 +0,0 @@
import CTAButton from "@/components/lib/CTAButton";
import paths from "@/utils/paths";
export default function OrderPlaced({ settings }) {
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-2 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
Your order is placed!
</h2>
<div className="flex flex-col gap-y-[25px] text-theme-text-secondary text-xs">
<p>
Your fine-tune will begin once payment is complete. If the payment
window did not automatically open - your checkout link is below.
</p>
<a
href={settings.checkoutUrl}
target="_blank"
rel="noreferrer"
className="text-sky-400 hover:underline hover:cursor-pointer"
>
{new URL(settings.checkoutUrl).origin}
</a>
<p className="text-xs text-theme-text-secondary">
Your fine-tune does not begin until this payment is completed.
</p>
<div className="flex flex-col gap-y-2">
<p className="text-theme-text-secondary font-medium">
Reference: <span className="font-normal">{settings.jobId}</span>
</p>
<p className="text-xs text-theme-text-secondary">
This reference id is how we will communicate with you about your
fine-tune training status. <b>Save this reference id.</b>
</p>
</div>
<div className="flex flex-col gap-y-2">
<p className="text-theme-text-secondary font-medium">
Contact: <span className="font-normal">{settings.email}</span>
</p>
<p className="text-xs text-theme-text-secondary">
Check the email above for order confirmation, status updates,
and more. Mintplex Labs will only contact you about your order
via email.
</p>
</div>
<div className="flex flex-col items-left gap-x-4 text-xs">
<a
href="https://docs.anythingllm.com/fine-tuning/overview"
target="_blank"
rel="noreferrer"
className="text-sky-400 hover:underline hover:cursor-pointer"
>
Documentation
</a>
<a
href="mailto:team@mintplexlabs.com"
className="text-sky-400 hover:underline hover:cursor-pointer"
>
Contact support
</a>
</div>
<p className="text-xs text-theme-text-secondary">
You can close this window or navigate away once you see the
confirmation email in your inbox.
</p>
</div>
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={() => (window.location.href = paths.home())}
>
Finish
</CTAButton>
</div>
</div>
</div>
);
}

View file

@ -1,241 +0,0 @@
import CTAButton from "@/components/lib/CTAButton";
import FineTuningSteps from "..";
export default function PrivacyHandling({ setSettings, setStep }) {
const handleAccept = () => {
setSettings((prev) => {
return { ...prev, agreedToPrivacy: true };
});
setStep(FineTuningSteps.privacy.next());
};
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
Data Handling Policy & Privacy
</h2>
<p className="text-theme-text-secondary text-sm">
Please accept the terms and conditions to continue with creation and
ordering of a fine-tune model. We take the handling of your data
very seriously and will only use your uploaded data for training the
model, after the model is created or the order is concluded,
completed, or canceled your information is automatically and
permanently deleted.
</p>
<div className="flex flex-col gap-y-2 text-theme-text-secondary text-xs font-semibold rounded-lg p-4 h-[60vh] overflow-y-auto bg-theme-bg-container mt-2">
<div className="text-xs">
<h1 className="text-theme-text-secondary">Privacy Policy</h1>
<p>Mintplex Labs Inc.</p>
<p>Effective Date: July 15, 2024</p>
</div>
<h2 className="text-theme-text-primary mt-4">1. Introduction</h2>
<p className="text-theme-text-secondary">
Welcome to Mintplex Labs Inc. ("we", "our", "us"). We are
committed to protecting your privacy and ensuring the security of
your personal information. This Privacy Policy describes how we
collect, use, and protect your information when you use our
services.
</p>
<h2 className="text-theme-text-primary mt-4">
2. Information We Collect
</h2>
<p className="text-theme-text-secondary">
When you place an order with us for tuning and large language
model (LLM) fulfillment, we collect certain personal information
from you, including but not limited to:
</p>
<ul className="list-disc pl-5 text-theme-text-secondary">
<li>Email address</li>
<li>Payment information</li>
<li>Uploaded training data</li>
</ul>
<h2 className="text-theme-text-primary mt-4">
3. Use of Information
</h2>
<p className="text-theme-text-secondary">
We use the information we collect for the following purposes:
</p>
<ul className="list-disc pl-5 text-theme-text-secondary">
<li>To process and fulfill your order</li>
<li>To communicate with you regarding your order</li>
<li>To improve our services</li>
</ul>
<h2 className="text-theme-text-primary mt-4">
4. Data Retention and Deletion
</h2>
<p className="text-theme-text-secondary">
Uploaded training data is only retained for the duration of the
model training. Upon training completion, failure, or order
cancellation, the user data is permanently deleted from our
storage.
</p>
<p className="text-theme-text-secondary">
If you partially complete the order flow and do not finalize your
order, any details and information associated with your order will
be deleted 1 hour from abandonment.
</p>
<p className="text-theme-text-secondary">
After you confirm receipt of your resulting model files, you can
request us to delete your model from our storage at any time.
Additionally, we may proactively reach out to you to confirm that
you have received your model so we can delete it from storage. Our
model file retention policy is 3 months, after which we will
contact you to confirm receipt so we can remove the model from our
storage.
</p>
<h2 className="text-theme-text-primary mt-4">
5. Data Storage and Security
</h2>
<p className="text-theme-text-secondary">
Our cloud storage provider is AWS. We have implement standard
encryption and protection policies to ensure the security of your
data. The storage solution has no public access, and all requests
for download URLs are pre-validated and signed by a minimal trust
program. Download URLs for the model file and associated outputs
are valid for 24 hours at a time. After expiration you can produce
refreshed links from https://my.mintplexlabs.com using the same
e-mail you used during checkout.
</p>
<h2 className="text-theme-text-primary mt-4">
6. Payment Processing
</h2>
<p className="text-theme-text-secondary">
We use Stripe as our payment processor. Your email may be shared
with Stripe for customer service and payment management purposes.
</p>
<h2 className="text-theme-text-primary mt-4">7. Data Sharing</h2>
<p className="text-theme-text-secondary">
We do not sell or share your personal information with third
parties except as necessary to provide our services, comply with
legal obligations, or protect our rights.
</p>
<h2 className="text-theme-text-primary mt-4">8. Your Rights</h2>
<p className="text-theme-text-secondary">
You have the right to access, correct, or delete your personal
information. If you wish to exercise these rights, please contact
us at{" "}
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
.
</p>
<h2 className="text-theme-text-primary mt-4">
9. California Privacy Rights
</h2>
<p className="text-theme-text-secondary">
Under the California Consumer Privacy Act as amended by the
California Privacy Rights Act (the "CCPA"), California residents
have additional rights beyond what is set out in this privacy
notice:
</p>
<ul className="list-disc pl-5 text-theme-text-secondary">
<li>
<strong>Right to Know:</strong> You have the right to request
information about the categories and specific pieces of personal
information we have collected about you, as well as the
categories of sources from which the information is collected,
the purpose for collecting such information, and the categories
of third parties with whom we share personal information.
</li>
<li>
<strong>Right to Delete:</strong> You have the right to request
the deletion of your personal information, subject to certain
exceptions.
</li>
<li>
<strong>Right to Correct:</strong> You have the right to request
the correction of inaccurate personal information that we have
about you.
</li>
<li>
<strong>Right to Opt-Out:</strong> You have the right to opt-out
of the sale of your personal information. Note, however, that we
do not sell your personal information.
</li>
<li>
<strong>Right to Non-Discrimination:</strong> You have the right
not to receive discriminatory treatment for exercising any of
your CCPA rights.
</li>
</ul>
<p className="text-theme-text-secondary">
<strong>Submitting a Request:</strong>
<br />
You may submit a request to know, delete, or correct your personal
information by contacting us at{" "}
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
. We will confirm your identity before processing your request and
respond within 45 days. If more time is needed, we will inform you
of the reason and extension period in writing. You may make a
request for your information twice every 12 months. If you are
making an erasure request, please include details of the
information you would like erased.
</p>
<p className="text-theme-text-secondary">
Please note that if you request that we remove your information,
we may retain some of the information for specific reasons, such
as to resolve disputes, troubleshoot problems, and as required by
law. Some information may not be completely removed from our
databases due to technical constraints and regular backups.
</p>
<p className="text-theme-text-secondary">
We will not discriminate against you for exercising any of your
CCPA rights.
</p>
<h2 className="text-theme-text-primary mt-4">10. Contact Us</h2>
<p className="text-theme-text-secondary">
If you have any questions or concerns about this Privacy Policy,
please contact us at{" "}
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
.
</p>
<h2 className="text-theme-text-primary mt-4">
11. Changes to This Privacy Policy
</h2>
<p className="text-theme-text-secondary">
We may update this Privacy Policy from time to time. We will
notify you of any changes by posting the new Privacy Policy on our
website. You are advised to review this Privacy Policy
periodically for any changes.
</p>
<p className="text-theme-text-secondary">
By using our services, you agree to the terms of this Privacy
Policy.
</p>
</div>
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
>
Agree and continue &rarr;
</CTAButton>
</div>
</div>
</div>
);
}

View file

@ -1,181 +0,0 @@
import CTAButton from "@/components/lib/CTAButton";
import FineTuningSteps from "..";
export default function TermsAndConditions({ setSettings, setStep }) {
const handleAccept = () => {
setSettings((prev) => {
return { ...prev, agreedToTerms: true };
});
setStep(FineTuningSteps.tos.next());
};
return (
<div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
<div className="bg-theme-bg-secondary rounded-xl flex-1 p-6">
<div className="w-full flex flex-col gap-y-3 max-w-[700px]">
<h2 className="text-base text-theme-text-primary font-semibold">
Terms and Conditions
</h2>
<p className="text-theme-text-secondary text-sm">
Please accept the terms and conditions to continue with creation and
ordering of a fine-tune model.
</p>
<div className="flex flex-col gap-y-2 text-theme-text-secondary text-xs font-semibold rounded-lg p-4 h-[60vh] overflow-y-auto bg-theme-bg-container mt-2">
<div className="text-xs">
<h1 className="text-theme-text-primary">
Mintplex Labs Inc. Fine-Tuning Terms of Service
</h1>
<p className="text-theme-text-secondary">
Last Updated: July 15, 2024
</p>
</div>
<p className="text-theme-text-secondary">
This Agreement is between Mintplex Labs Inc. ("Company") and the
customer ("Customer") accessing or using the services provided by
the Company. By signing up, accessing, or using the services,
Customer indicates its acceptance of this Agreement and agrees to
be bound by the terms and conditions outlined below.
</p>
<h2 className="text-theme-text-primary mt-4">
1. Services Provided
</h2>
<p className="text-theme-text-secondary">
Mintplex Labs Inc. provides model fine-tuning services for
customers. The deliverable for these services is a download link
to the output ".GGUF" file that can be used by the Customer for
Large-Language text inferencing.
</p>
<h2 className="text-theme-text-primary mt-4">2. Payment Terms</h2>
<ul className="list-disc pl-5 text-theme-text-secondary">
<li>
<strong>One-Time Payment:</strong> A one-time payment is
required before the execution of the training.
</li>
<li>
<strong>Payment Due Date:</strong> Payment is due upon order
placement.
</li>
<li>
<strong>Refund Policy:</strong> Payments are refundable in the
event of training failure or if the Company fails to deliver the
complete model file to the Customer.
</li>
</ul>
<h2 className="text-theme-text-primary mt-4">3. Order Form</h2>
<ul className="list-disc pl-5 text-theme-text-secondary">
<li>
<strong>Service:</strong> Model fine-tuning
</li>
<li>
<strong>Payment Amount:</strong> As specified in the order form
</li>
<li>
<strong>Payment Due Date:</strong> Upon order placement
</li>
</ul>
<h2 className="text-theme-text-primary mt-4">
4. Customer Responsibilities
</h2>
<p className="text-theme-text-secondary">
The Customer must provide all necessary data and information
required for model fine-tuning.
</p>
<p className="text-theme-text-secondary">
The Customer must ensure timely payment as per the terms mentioned
above.
</p>
<p className="text-theme-text-secondary">
The Customer understands the data collected for tuning will be
stored to a private cloud storage location temporarily while
training is in progress.
</p>
<p className="text-theme-text-secondary">
The Customer understands the data collected for tuning will be
fully deleted once the order is completed or canceled by the
Company.
</p>
<p className="text-theme-text-secondary">
The Customer understands and has reviewed the Privacy Policy for
Fine-Tuning by the Company.
</p>
<h2 className="text-theme-text-primary mt-4">5. Refund Policy</h2>
<p className="text-theme-text-secondary">
Refunds will be processed in the event of training failure or if
the complete model file is not delivered to the Customer. Refunds
will be issued to the original payment method within 30 days of
the refund request.
</p>
<h2 className="text-theme-text-primary mt-4">6. Governing Law</h2>
<p className="text-theme-text-secondary">
This Agreement shall be governed by and construed in accordance
with the laws of the State of California.
</p>
<h2 className="text-theme-text-primary mt-4">
7. Dispute Resolution
</h2>
<p className="text-theme-text-secondary">
Any disputes arising out of or in connection with this Agreement
shall be resolved in the state or federal courts located in
California.
</p>
<h2 className="text-theme-text-primary mt-4">8. Notices</h2>
<p className="text-theme-text-secondary">
All notices under this Agreement shall be in writing and shall be
deemed given when delivered personally, sent by confirmed email,
or sent by certified or registered mail, return receipt requested,
and addressed to the respective parties as follows:
</p>
<p className="text-theme-text-secondary">
For Company:{" "}
<a
href="mailto:team@mintplexlabs.com"
className="text-blue-400 hover:underline"
>
team@mintplexlabs.com
</a>
</p>
<p className="text-theme-text-secondary">
For Customer: The main email address on Customer's account
</p>
<h2 className="text-theme-text-primary mt-4">9. Amendments</h2>
<p className="text-theme-text-secondary">
The Company reserves the right to amend these terms at any time by
providing notice to the Customer. The Customer's continued use of
the services after such amendments will constitute acceptance of
the amended terms.
</p>
<h2 className="text-theme-text-primary mt-4">10. Indemnity</h2>
<p className="text-theme-text-secondary">
The Customer agrees to indemnify, defend, and hold harmless
Mintplex Labs Inc., its affiliates, and their respective officers,
directors, employees, agents, and representatives from and against
any and all claims, liabilities, damages, losses, costs, expenses,
fees (including reasonable attorneys' fees and court costs) that
arise from or relate to: (a) the Customer's use of the services;
(b) any violation of this Agreement by the Customer; (c) any
breach of any representation, warranty, or covenant made by the
Customer; or (d) the Customer's violation of any rights of another
person or entity.
</p>
</div>
<CTAButton
className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
onClick={handleAccept}
>
Agree and continue &rarr;
</CTAButton>
</div>
</div>
</div>
);
}

View file

@ -1,148 +0,0 @@
import { isMobile } from "react-device-detect";
import { useState } from "react";
import Sidebar from "@/components/Sidebar";
import Introduction from "./Introduction";
import PrivacyPolicy from "./Privacy";
import TermsAndConditions from "./TermsAndConditions";
import Fulfillment from "./FulfillmentPolicy";
import OrderDetails from "./OrderDetails";
import DataUpload from "./DataUpload";
import Confirmation from "./Confirmation";
import OrderPlaced from "./OrderPlaced";
/**
* @typedef OrderSettings
* @property {string} email
* @property {string} baseModel
* @property {string} modelName
* @property {boolean} agreedToTerms
* @property {boolean} agreedToPrivacy
* @property {string} modelName
* @property {string|null} checkoutUrl
* @property {string|null} jobId
* @property {{slugs: string[], feedback: boolean|null}} trainingData
* @property {{pricing: {usd: number}, availableBaseModels: string[]}} tuningInfo
*/
const FineTuningSteps = {
intro: {
name: "1. Introduction to Fine-Tuning",
next: () => "privacy",
component: ({ settings, setSettings, setStep }) => (
<Introduction
settings={settings}
setSettings={setSettings}
setStep={setStep}
/>
),
},
privacy: {
name: "2. How your data is handled",
next: () => "tos",
component: ({ settings, setSettings, setStep }) => (
<PrivacyPolicy
settings={settings}
setSettings={setSettings}
setStep={setStep}
/>
),
},
tos: {
name: "3. Terms of service",
next: () => "fulfillment",
component: ({ settings, setSettings, setStep }) => (
<TermsAndConditions
settings={settings}
setSettings={setSettings}
setStep={setStep}
/>
),
},
fulfillment: {
name: "4. Fulfillment terms",
next: () => "order-details",
component: ({ settings, setSettings, setStep }) => (
<Fulfillment
settings={settings}
setSettings={setSettings}
setStep={setStep}
/>
),
},
"order-details": {
name: "5. Model details & information",
next: () => "data-selection",
component: ({ settings, setSettings, setStep }) => (
<OrderDetails
settings={settings}
setSettings={setSettings}
setStep={setStep}
/>
),
},
"data-selection": {
name: "6. Data selection",
next: () => "confirmation",
component: ({ settings, setSettings, setStep }) => (
<DataUpload
settings={settings}
setSettings={setSettings}
setStep={setStep}
/>
),
},
confirmation: {
name: "7. Review and Submit",
next: () => "done",
component: ({ settings, setSettings, setStep }) => (
<Confirmation
settings={settings}
setSettings={setSettings}
setStep={setStep}
/>
),
},
done: {
name: "8. Order placed",
next: () => "done",
component: ({ settings }) => <OrderPlaced settings={settings} />,
},
};
export function FineTuningCreationLayout({ setStep, children }) {
const [settings, setSettings] = useState({
email: null,
baseModel: null,
modelName: null,
agreedToTerms: false,
agreedToPrivacy: false,
data: {
workspaceSlugs: [],
feedback: false,
},
tuningInfo: {
pricing: {
usd: 0.0,
},
availableBaseModels: [],
},
checkoutUrl: null,
jobId: null,
});
return (
<div
id="fine-tune-create-order-container"
className="w-screen h-screen overflow-hidden bg-theme-bg-container flex md:mt-0 mt-6"
>
<Sidebar />
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] w-full h-full flex"
>
{children(settings, setSettings, setStep)}
</div>
</div>
);
}
export default FineTuningSteps;

View file

@ -1,89 +0,0 @@
import React, { useState } from "react";
import FineTuningSteps, { FineTuningCreationLayout } from "./Steps";
import { Sparkle } from "@phosphor-icons/react";
import { isMobile } from "react-device-detect";
function SideBarSelection({ setStep, currentStep }) {
const currentIndex = Object.keys(FineTuningSteps).indexOf(currentStep);
return (
<div
className={`bg-white/5 light:bg-white text-theme-text-primary rounded-xl py-1 px-4 shadow-lg ${
isMobile ? "w-full" : "min-w-[360px] w-fit"
}`}
>
{Object.entries(FineTuningSteps).map(([stepKey, props], index) => {
const isSelected = currentStep === stepKey;
const isLast = index === Object.keys(FineTuningSteps).length - 1;
const isDone =
currentIndex === Object.keys(FineTuningSteps).length - 1 ||
index < currentIndex;
return (
<div
key={stepKey}
className={[
"py-3 flex items-center justify-between transition-all duration-300",
isSelected ? "rounded-t-xl" : "",
isLast
? ""
: "border-b border-white/10 light:border-[#026AA2]/10",
].join(" ")}
>
{isDone || isSelected ? (
<button
onClick={() => setStep(stepKey)}
className="border-none hover:underline text-sm font-medium text-theme-text-primary"
>
{props.name}
</button>
) : (
<div className="text-sm text-theme-text-secondary font-medium">
{props.name}
</div>
)}
<div className="flex items-center gap-x-2">
{isDone ? (
<div className="w-[14px] h-[14px] rounded-full border border-[#32D583] flex items-center justify-center">
<div className="w-[5.6px] h-[5.6px] rounded-full bg-[#6CE9A6]"></div>
</div>
) : (
<div
className={`w-[14px] h-[14px] rounded-full border border-theme-text-primary ${
isSelected ? "animate-pulse" : "opacity-50"
}`}
/>
)}
</div>
</div>
);
})}
</div>
);
}
export default function FineTuningFlow() {
const [step, setStep] = useState("intro");
const StepPage = FineTuningSteps.hasOwnProperty(step)
? FineTuningSteps[step]
: FineTuningSteps.intro;
return (
<FineTuningCreationLayout setStep={setStep}>
{(settings, setSettings, setStep) => (
<div className="flex-1 flex h-full">
<div className="flex flex-col gap-y-[18px] p-4 mt-10 w-[360px] flex-shrink-0">
<div className="text-theme-text-primary flex items-center gap-x-2">
<Sparkle size={24} />
<p className="text-lg font-medium">Custom Fine-Tuned Model</p>
</div>
<SideBarSelection setStep={setStep} currentStep={step} />
</div>
<div className="flex-1 overflow-y-auto p-4 mt-10 pb-[74px] h-screen">
<div className="ml-8">
{StepPage.component({ settings, setSettings, setStep })}
</div>
</div>
</div>
)}
</FineTuningCreationLayout>
);
}

View file

@ -7,10 +7,9 @@ import useQuery from "@/hooks/useQuery";
import ChatRow from "./ChatRow";
import showToast from "@/utils/toast";
import System from "@/models/system";
import { CaretDown, Download, Sparkle, Trash } from "@phosphor-icons/react";
import { CaretDown, Download, Trash } from "@phosphor-icons/react";
import { saveAs } from "file-saver";
import { useTranslation } from "react-i18next";
import paths from "@/utils/paths";
import { CanViewChatHistory } from "@/components/CanViewChatHistory";
const exportOptions = {
@ -163,22 +162,13 @@ export default function WorkspaceChats() {
</div>
</div>
{chats.length > 0 && (
<>
<button
onClick={handleClearAllChats}
className="flex items-center gap-x-2 px-4 py-1 border hover:border-transparent light:border-theme-sidebar-border border-white/40 text-white/40 light:text-theme-text-secondary rounded-lg bg-transparent hover:light:text-theme-bg-primary hover:text-theme-text-primary text-xs font-semibold hover:bg-red-500 shadow-[0_4px_14px_rgba(0,0,0,0.25)] h-[34px] w-fit"
>
<Trash size={18} weight="bold" />
Clear Chats
</button>
<a
href={paths.orderFineTune()}
className="flex items-center gap-x-2 px-4 py-1 border hover:border-transparent border-yellow-300 light:border-yellow-600 text-yellow-300/80 light:text-yellow-600 rounded-lg bg-transparent hover:light:text-yellow-800 hover:text-theme-text-primary text-xs font-semibold hover:bg-yellow-300/75 shadow-[0_4px_14px_rgba(0,0,0,0.25)] h-[34px] w-fit"
>
<Sparkle size={18} weight="bold" />
Order Fine-Tune Model
</a>
</>
<button
onClick={handleClearAllChats}
className="flex items-center gap-x-2 px-4 py-1 border hover:border-transparent light:border-theme-sidebar-border border-white/40 text-white/40 light:text-theme-text-secondary rounded-lg bg-transparent hover:light:text-theme-bg-primary hover:text-theme-text-primary text-xs font-semibold hover:bg-red-500 shadow-[0_4px_14px_rgba(0,0,0,0.25)] h-[34px] w-fit"
>
<Trash size={18} weight="bold" />
Clear Chats
</button>
)}
</div>
<p className="text-xs leading-[18px] font-base text-theme-text-secondary mt-2">

View file

@ -76,9 +76,6 @@ export default {
apiDocs: () => {
return `${API_BASE}/docs`;
},
orderFineTune: () => {
return `/fine-tuning`;
},
settings: {
users: () => {
return `/settings/users`;

View file

@ -1,108 +0,0 @@
const { FineTuning } = require("../../models/fineTuning");
const { Telemetry } = require("../../models/telemetry");
const { WorkspaceChats } = require("../../models/workspaceChats");
const { reqBody } = require("../../utils/http");
const {
flexUserRoleValid,
ROLES,
} = require("../../utils/middleware/multiUserProtected");
const { validatedRequest } = require("../../utils/middleware/validatedRequest");
function fineTuningEndpoints(app) {
if (!app) return;
app.get(
"/experimental/fine-tuning/check-eligible",
[validatedRequest, flexUserRoleValid([ROLES.admin])],
async (_request, response) => {
try {
const chatCount = await WorkspaceChats.count();
response
.status(200)
.json({ eligible: chatCount >= FineTuning.recommendedMinDataset });
} catch (e) {
console.error(e);
response.status(500).end();
}
}
);
app.get(
"/experimental/fine-tuning/info",
[validatedRequest, flexUserRoleValid([ROLES.admin])],
async (_request, response) => {
try {
const fineTuningInfo = await FineTuning.getInfo();
await Telemetry.sendTelemetry("fine_tuning_interest", {
step: "information",
});
response.status(200).json(fineTuningInfo);
} catch (e) {
console.error(e);
response.status(500).end();
}
}
);
app.post(
"/experimental/fine-tuning/dataset",
[validatedRequest, flexUserRoleValid([ROLES.admin])],
async (request, response) => {
try {
const { slugs = [], feedback = null } = reqBody(request);
if (!Array.isArray(slugs) || slugs.length === 0) {
return response.status(200).json({
count: 0,
recommendedMin: FineTuning.recommendedMinDataset,
});
}
const count = await FineTuning.datasetSize(slugs, feedback);
await Telemetry.sendTelemetry("fine_tuning_interest", {
step: "uploaded_dataset",
});
response
.status(200)
.json({ count, recommendedMin: FineTuning.recommendedMinDataset });
} catch (e) {
console.error(e);
response.status(500).end();
}
}
);
app.post(
"/experimental/fine-tuning/order",
[validatedRequest, flexUserRoleValid([ROLES.admin])],
async (request, response) => {
try {
const { email, baseModel, modelName, trainingData } = reqBody(request);
if (
!email ||
!baseModel ||
!modelName ||
!trainingData ||
!trainingData?.slugs.length
)
throw new Error("Invalid order details");
const { jobId, checkoutUrl } = await FineTuning.newOrder({
email,
baseModel,
modelName,
trainingData,
});
await Telemetry.sendTelemetry("fine_tuning_interest", {
step: "created_order",
jobId,
});
response.status(200).json({ jobId, checkoutUrl });
} catch (e) {
console.error(e);
response.status(500).end();
}
}
);
}
module.exports = { fineTuningEndpoints };

View file

@ -1,4 +1,3 @@
const { fineTuningEndpoints } = require("./fineTuning");
const { liveSyncEndpoints } = require("./liveSync");
const { importedAgentPluginEndpoints } = require("./imported-agent-plugins");
@ -7,7 +6,6 @@ const { importedAgentPluginEndpoints } = require("./imported-agent-plugins");
// When a feature is promoted it should be removed from here and added to the appropriate scope.
function experimentalEndpoints(router) {
liveSyncEndpoints(router);
fineTuningEndpoints(router);
importedAgentPluginEndpoints(router);
}

View file

@ -1,222 +0,0 @@
const { default: slugify } = require("slugify");
const { safeJsonParse } = require("../utils/http");
const { Telemetry } = require("./telemetry");
const { Workspace } = require("./workspace");
const { WorkspaceChats } = require("./workspaceChats");
const fs = require("fs");
const path = require("path");
const { v4: uuidv4 } = require("uuid");
const tmpStorage =
process.env.NODE_ENV === "development"
? path.resolve(__dirname, `../storage/tmp`)
: path.resolve(
process.env.STORAGE_DIR ?? path.resolve(__dirname, `../storage`),
`tmp`
);
const FineTuning = {
API_BASE:
process.env.NODE_ENV === "development"
? process.env.FINE_TUNING_ORDER_API
: "https://finetuning-wxich7363q-uc.a.run.app",
recommendedMinDataset: 50,
standardPrompt:
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.",
/**
* Get the information for the Fine-tuning product to display in various frontends
* @returns {Promise<{
* productDetails: {
* name: string,
* description: string,
* icon: string,
* active: boolean,
* },
* pricing: {
* usd: number,
* },
* availableBaseModels: string[]
* }>}
*/
getInfo: async function () {
return fetch(`${this.API_BASE}/info`, {
method: "GET",
headers: {
Accepts: "application/json",
},
})
.then((res) => {
if (!res.ok)
throw new Error("Could not fetch fine-tuning information endpoint");
return res.json();
})
.catch((e) => {
console.error(e);
return null;
});
},
/**
* Get the Dataset size for a training set.
* @param {string[]} workspaceSlugs
* @param {boolean|null} feedback
* @returns {Promise<number>}
*/
datasetSize: async function (workspaceSlugs = [], feedback = null) {
const workspaceIds = await Workspace.where({
slug: {
in: workspaceSlugs.map((slug) => String(slug)),
},
}).then((results) => results.map((res) => res.id));
const count = await WorkspaceChats.count({
workspaceId: {
in: workspaceIds,
},
...(feedback === true ? { feedbackScore: true } : {}),
});
return count;
},
_writeToTempStorage: function (data) {
const tmpFilepath = path.resolve(tmpStorage, `${uuidv4()}.json`);
if (!fs.existsSync(tmpStorage))
fs.mkdirSync(tmpStorage, { recursive: true });
fs.writeFileSync(tmpFilepath, JSON.stringify(data, null, 4));
return tmpFilepath;
},
_rmTempDatafile: function (datafileLocation) {
if (!datafileLocation || !fs.existsSync(datafileLocation)) return;
fs.rmSync(datafileLocation);
},
_uploadDatafile: async function (datafileLocation, uploadConfig) {
try {
const fileBuffer = fs.readFileSync(datafileLocation);
const formData = new FormData();
Object.entries(uploadConfig.fields).forEach(([key, value]) =>
formData.append(key, value)
);
formData.append("file", fileBuffer);
const response = await fetch(uploadConfig.url, {
method: "POST",
body: formData,
});
console.log("File upload returned code:", response.status);
return true;
} catch (error) {
console.error("Error uploading file:", error.message);
return false;
}
},
_buildSystemPrompt: function (chat, prompt = null) {
const sources = safeJsonParse(chat.response)?.sources || [];
const contextTexts = sources.map((source) => source.text);
const context =
sources.length > 0
? "\nContext:\n" +
contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")
: "";
return `${prompt ?? this.standardPrompt}${context}`;
},
_createTempDataFile: async function ({ slugs, feedback }) {
const workspacePromptMap = {};
const workspaces = await Workspace.where({
slug: {
in: slugs.map((slug) => String(slug)),
},
});
workspaces.forEach((ws) => {
workspacePromptMap[ws.id] = ws.openAiPrompt ?? this.standardPrompt;
});
const chats = await WorkspaceChats.whereWithData({
workspaceId: {
in: workspaces.map((ws) => ws.id),
},
...(feedback === true ? { feedbackScore: true } : {}),
});
const preparedData = chats.map((chat) => {
const responseJson = safeJsonParse(chat.response);
return {
instruction: this._buildSystemPrompt(
chat,
workspacePromptMap[chat.workspaceId]
),
input: chat.prompt,
output: responseJson.text,
};
});
const tmpFile = this._writeToTempStorage(preparedData);
return tmpFile;
},
/**
* Generate fine-tune order request
* @param {object} data
* @returns {Promise<{jobId:string, uploadParams: object, configReady: boolean, checkoutUrl:string}>}
*/
_requestOrder: async function (data = {}) {
return await fetch(`${this.API_BASE}/order/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accepts: "application/json",
},
body: JSON.stringify(data),
})
.then((res) => {
if (!res.ok) throw new Error("Could not create fine-tune order");
return res.json();
})
.catch((e) => {
console.error(e);
return {
jobId: null,
uploadParams: null,
configReady: null,
checkoutUrl: null,
};
});
},
/**
* Sanitizes the slugifies the model name to prevent issues during processing.
* only a-zA-Z0-9 are okay for model names. If name is totally invalid it becomes a uuid.
* @param {string} modelName - provided model name
* @returns {string}
*/
_cleanModelName: function (modelName = "") {
if (!modelName) return uuidv4();
const sanitizedName = modelName.replace(/[^a-zA-Z0-9]/g, " ");
return slugify(sanitizedName);
},
newOrder: async function ({ email, baseModel, modelName, trainingData }) {
const datafileLocation = await this._createTempDataFile(trainingData);
const order = await this._requestOrder({
email,
baseModel,
modelName: this._cleanModelName(modelName),
orderExtras: { platform: Telemetry.runtime() },
});
const uploadComplete = await this._uploadDatafile(
datafileLocation,
order.uploadParams
);
if (!uploadComplete)
throw new Error("Data file upload failed. Order could not be created.");
this._rmTempDatafile(datafileLocation);
return { jobId: order.jobId, checkoutUrl: order.checkoutUrl };
},
};
module.exports = { FineTuning };