Skip to content

feature: added a/b testing infra to backend #3907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
442 changes: 442 additions & 0 deletions clients/ts-sdk/openapi.json

Large diffs are not rendered by default.

170 changes: 170 additions & 0 deletions clients/ts-sdk/src/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

export type APIVersion = 'V1' | 'V2';

export type AbTestReqBody = {
experiment_id: string;
user_id: string;
};

export type AddChunkToGroupReqPayload = {
/**
* Id of the chunk to make a member of the group.
Expand Down Expand Up @@ -970,6 +975,11 @@ export type CreateDatasetReqPayload = {
tracking_id?: (string) | null;
};

export type CreateExperimentReqBody = {
experiment_config: ExperimentConfig;
name: string;
};

/**
* Will use [chunkr.ai](https://chunkr.ai) to process the file when this object is defined. See [docs.chunkr.ai/api-references/task/create-task](https://docs.chunkr.ai/api-references/task/create-task) for detailed information about what each field on this request payload does.
*/
Expand Down Expand Up @@ -1908,6 +1918,25 @@ export type EventsForTopicResponse = {
events: Array<EventData>;
};

export type Experiment = {
control_name: string;
control_split: number;
created_at: string;
dataset_id: string;
id: string;
name: string;
t1_name: string;
t1_split: number;
updated_at: string;
};

export type ExperimentConfig = {
control_name: string;
control_split: number;
t1_name: string;
t1_split: number;
};

export type ExtendedOrganizationUsageCount = {
bytes_ingested: number;
chunk_count: number;
Expand Down Expand Up @@ -4643,6 +4672,12 @@ export type UpdateDatasetReqPayload = {
tracking_id?: (string) | null;
};

export type UpdateExperimentReqBody = {
experiment_config?: ((ExperimentConfig) | null);
id: string;
name?: (string) | null;
};

export type UpdateGroupByTrackingIDReqPayload = {
/**
* Description to assign to the chunk_group. Convenience field for you to avoid having to remember what the group is for. If not provided, the description will not be updated.
Expand Down Expand Up @@ -4796,6 +4831,12 @@ export type UserOrganization = {
user_id: string;
};

export type UserTreatmentResponse = {
experiment_id: string;
treatment_name: string;
user_id: string;
};

export type V1RecommendChunksResponseBody = Array<ChunkMetadataWithScore>;

export type WorkerEvent = {
Expand Down Expand Up @@ -5857,6 +5898,64 @@ export type CreateEtlJobData = {

export type CreateEtlJobResponse = (void);

export type GetExperimentsData = {
/**
* The dataset id to use for the request
*/
trDataset: string;
};

export type GetExperimentsResponse = (Array<Experiment>);

export type CreateExperimentData = {
/**
* JSON request payload to create a new experiment
*/
requestBody: CreateExperimentReqBody;
/**
* The dataset id to use for the request
*/
trDataset: string;
};

export type CreateExperimentResponse = (Experiment);

export type UpdateExperimentData = {
/**
* JSON request payload to update an experiment
*/
requestBody: UpdateExperimentReqBody;
/**
* The dataset id to use for the request
*/
trDataset: string;
};

export type UpdateExperimentResponse = (Experiment);

export type AbTestData = {
/**
* JSON request payload to get a user's treatment
*/
requestBody: AbTestReqBody;
/**
* The dataset id to use for the request
*/
trDataset: string;
};

export type AbTestResponse = (UserTreatmentResponse);

export type DeleteExperimentData = {
experimentId: string;
/**
* The dataset id to use for the request
*/
trDataset: string;
};

export type DeleteExperimentResponse = (void);

export type UploadFileHandlerData = {
/**
* JSON request payload to upload a file
Expand Down Expand Up @@ -7572,6 +7671,77 @@ export type $OpenApiTs = {
};
};
};
'/api/experiment': {
get: {
req: GetExperimentsData;
res: {
/**
* Experiments retrieved successfully
*/
200: Array<Experiment>;
/**
* Service error relating to getting the experiments
*/
400: ErrorResponseBody;
};
};
post: {
req: CreateExperimentData;
res: {
/**
* Experiment created successfully
*/
200: Experiment;
/**
* Service error relating to creating the experiment
*/
400: ErrorResponseBody;
};
};
put: {
req: UpdateExperimentData;
res: {
/**
* Experiment updated successfully
*/
200: Experiment;
/**
* Service error relating to updating the experiment
*/
400: ErrorResponseBody;
};
};
};
'/api/experiment/ab-test': {
post: {
req: AbTestData;
res: {
/**
* User treatment response
*/
200: UserTreatmentResponse;
/**
* Service error relating to getting the user's treatment
*/
400: ErrorResponseBody;
};
};
};
'/api/experiment/{experiment_id}': {
delete: {
req: DeleteExperimentData;
res: {
/**
* Experiment deleted successfully
*/
204: void;
/**
* Service error relating to deleting the experiment
*/
400: ErrorResponseBody;
};
};
};
'/api/file': {
post: {
req: UploadFileHandlerData;
Expand Down
8 changes: 4 additions & 4 deletions frontends/dashboard/src/pages/dataset/PublicPageSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ const PublicPageControls = () => {
{loadingDefaultConfig() ? "Loading..." : "Auto Configure"}
</button>
</div>
<div class="mb-6 py-2 flex content-center items-center gap-1.5 gap-x-2.5">
<div class="mb-6 flex content-center items-center gap-1.5 gap-x-2.5 py-2">
<span class="font-medium">Published Url:</span>{" "}
<a class="text-magenta-400" href={publicUrl()} target="_blank">
{publicUrl()}
Expand Down Expand Up @@ -359,11 +359,11 @@ const PublicPageControls = () => {
options={["light", "dark"]}
/>
</div>
<div class="grow max-w-[50%]">
<div class="max-w-[50%] grow">
<For each={docColors()}>
{(color) => (
<button
class="w-6 h-6 rounded-lg"
class="h-6 w-6 rounded-lg"
style={{ "background-color": color }}
onClick={() => {
setExtraParams("brandColor", color);
Expand Down Expand Up @@ -1756,7 +1756,7 @@ export const SingleProductOptions = () => {
});
}}
disabled={loadingAutoFill()}
class="inline-flex justify-center rounded-md bg-magenta-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-magenta-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-magenta-900 disabled:animate-pulse min-w-[130px]"
class="inline-flex min-w-[130px] justify-center rounded-md bg-magenta-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-magenta-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-magenta-900 disabled:animate-pulse"
>
{loadingAutoFill() ? "Loading..." : "Auto Fill"}
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS experiments;
DROP TABLE IF EXISTS experiment_user_assignments;
28 changes: 28 additions & 0 deletions server/ch_migrations/1747440134_add_experiments_table/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
CREATE TABLE IF NOT EXISTS experiments (
id UUID,
name String,
t1_name String,
t1_split Float32,
control_name String,
control_split Float32,
dataset_id UUID,
created_at DateTime DEFAULT now(),
updated_at DateTime DEFAULT now(),
)
ORDER BY (created_at, id)
PARTITION BY
(dataset_id);

CREATE TABLE IF NOT EXISTS experiment_user_assignments (
id UUID,
experiment_id UUID,
user_id String,
dataset_id UUID,
treatment_name String,
created_at DateTime DEFAULT now(),
updated_at DateTime DEFAULT now(),
)
ORDER BY (created_at, id)
PARTITION BY
(experiment_id);

86 changes: 86 additions & 0 deletions server/src/data/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8901,6 +8901,92 @@ impl Default for ContextOptions {
}
}

#[derive(Debug, Serialize, Deserialize, ToSchema, Clone, Row)]
pub struct ExperimentClickhouse {
#[serde(with = "clickhouse::serde::uuid")]
pub id: uuid::Uuid,
pub name: String,
pub t1_name: String,
pub t1_split: f32,
pub control_name: String,
pub control_split: f32,
#[serde(with = "clickhouse::serde::uuid")]
pub dataset_id: uuid::Uuid,
#[serde(with = "clickhouse::serde::time::datetime")]
pub created_at: OffsetDateTime,
#[serde(with = "clickhouse::serde::time::datetime")]
pub updated_at: OffsetDateTime,
}

#[derive(Debug, Serialize, Deserialize, ToSchema, Clone)]
pub struct Experiment {
pub id: uuid::Uuid,
pub name: String,
pub t1_name: String,
pub t1_split: f32,
pub control_name: String,
pub control_split: f32,
pub dataset_id: uuid::Uuid,
pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}

impl From<Experiment> for ExperimentClickhouse {
fn from(experiment: Experiment) -> Self {
ExperimentClickhouse {
id: experiment.id,
name: experiment.name,
t1_name: experiment.t1_name,
t1_split: experiment.t1_split,
control_name: experiment.control_name,
control_split: experiment.control_split,
dataset_id: experiment.dataset_id,
created_at: OffsetDateTime::from_unix_timestamp(experiment.created_at.timestamp())
.unwrap(),
updated_at: OffsetDateTime::from_unix_timestamp(experiment.updated_at.timestamp())
.unwrap(),
}
}
}

impl From<ExperimentClickhouse> for Experiment {
fn from(experiment: ExperimentClickhouse) -> Self {
Experiment {
id: experiment.id,
name: experiment.name,
t1_name: experiment.t1_name,
t1_split: experiment.t1_split,
control_name: experiment.control_name,
control_split: experiment.control_split,
dataset_id: experiment.dataset_id,
created_at: chrono::NaiveDateTime::from_timestamp(
experiment.created_at.unix_timestamp(),
0,
),
updated_at: chrono::NaiveDateTime::from_timestamp(
experiment.updated_at.unix_timestamp(),
0,
),
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, Row, ToSchema)]
pub struct ExperimentUserAssignment {
#[serde(with = "clickhouse::serde::uuid")]
pub id: uuid::Uuid,
#[serde(with = "clickhouse::serde::uuid")]
pub experiment_id: uuid::Uuid,
pub user_id: String,
#[serde(with = "clickhouse::serde::uuid")]
pub dataset_id: uuid::Uuid,
pub treatment_name: String,
#[serde(with = "clickhouse::serde::time::datetime")]
pub created_at: OffsetDateTime,
#[serde(with = "clickhouse::serde::time::datetime")]
pub updated_at: OffsetDateTime,
}

#[derive(Debug, Serialize, Deserialize, ToSchema, Clone, Default)]
/// LLM options to use for the completion. If not specified, this defaults to the dataset's LLM options.
pub struct LLMOptions {
Expand Down
Loading