Skip to content

Commit 36318b0

Browse files
authored
Merge pull request #1239 from Kiln-AI/KIL-517/misc-spec-bugs
KIL-517 Fix misc spec builder bugs and improvements
2 parents e01a8cb + 569dacd commit 36318b0

File tree

9 files changed

+220
-143
lines changed

9 files changed

+220
-143
lines changed

app/web_ui/src/lib/utils/input_validators.test.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
tool_name_validator,
66
skill_name_validator,
77
normalize_skill_name,
8+
normalize_filename_string,
89
filename_string_short_validator,
910
} from "./input_validators"
1011

@@ -757,14 +758,14 @@ describe("input_validators", () => {
757758
expect(result).toBeNull()
758759
})
759760

760-
it("should return error for trailing whitespace", () => {
761+
it("should allow trailing whitespace (normalized on submit)", () => {
761762
const result = filename_string_short_validator("Correctness ")
762-
expect(result).toBe("Cannot have leading or trailing whitespace")
763+
expect(result).toBeNull()
763764
})
764765

765-
it("should return error for leading whitespace", () => {
766+
it("should allow leading whitespace (normalized on submit)", () => {
766767
const result = filename_string_short_validator(" Leading Space")
767-
expect(result).toBe("Cannot have leading or trailing whitespace")
768+
expect(result).toBeNull()
768769
})
769770

770771
it("should return error for consecutive underscores", () => {
@@ -812,9 +813,9 @@ describe("input_validators", () => {
812813
expect(result).toBe("Cannot start or end with an underscore")
813814
})
814815

815-
it("should return error for consecutive whitespace", () => {
816+
it("should allow consecutive whitespace (normalized on submit)", () => {
816817
const result = filename_string_short_validator("consecutive spaces")
817-
expect(result).toBe("Cannot contain consecutive whitespace")
818+
expect(result).toBeNull()
818819
})
819820

820821
it("should return error for colon", () => {
@@ -830,4 +831,22 @@ describe("input_validators", () => {
830831
})
831832
})
832833
})
834+
835+
describe("normalize_filename_string", () => {
836+
it("should trim leading and trailing whitespace", () => {
837+
expect(normalize_filename_string(" hello ")).toBe("hello")
838+
})
839+
840+
it("should collapse consecutive whitespace", () => {
841+
expect(normalize_filename_string("hello world")).toBe("hello world")
842+
})
843+
844+
it("should handle combined leading, trailing, and consecutive whitespace", () => {
845+
expect(normalize_filename_string(" hello world ")).toBe("hello world")
846+
})
847+
848+
it("should not modify a clean string", () => {
849+
expect(normalize_filename_string("hello world")).toBe("hello world")
850+
})
851+
})
833852
})

app/web_ui/src/lib/utils/input_validators.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,11 @@ export function filename_string_validator(
102102
return 'Cannot contain any of these characters: / \\ ? % * : | " < > . , ; = or newlines'
103103
}
104104

105-
// Check for leading/trailing whitespace or underscores
106-
if (name !== name.trim()) {
107-
return "Cannot have leading or trailing whitespace"
108-
}
109-
if (name.startsWith("_") || name.endsWith("_")) {
105+
// Check for leading/trailing underscores
106+
if (name.trim().startsWith("_") || name.trim().endsWith("_")) {
110107
return "Cannot start or end with an underscore"
111108
}
112109

113-
// Check for consecutive whitespace
114-
if (/\s\s+/.test(name)) {
115-
return "Cannot contain consecutive whitespace"
116-
}
117-
118110
// Check for consecutive underscores
119111
if (name.includes("__")) {
120112
return "Cannot contain consecutive underscores"
@@ -123,6 +115,12 @@ export function filename_string_validator(
123115
return null
124116
}
125117

118+
// Normalizes a filename string by trimming whitespace and collapsing consecutive spaces.
119+
// Apply before submitting to the backend.
120+
export function normalize_filename_string(value: string): string {
121+
return value.trim().replace(/\s\s+/g, " ")
122+
}
123+
126124
// FilenameStringShort validator (max 32 characters) - used for eval score names, task requirement names
127125
export const filename_string_short_validator: (
128126
value: unknown,

app/web_ui/src/lib/utils/few_shot_example.ts renamed to app/web_ui/src/lib/utils/task_sample_example.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { client } from "$lib/api_client"
22
import type { TaskRun } from "$lib/types"
33

44
/**
5-
* A few-shot example consisting of an input/output pair.
5+
* A task sample example consisting of an input/output pair.
66
*/
7-
export type FewShotExample = {
7+
export type TaskSampleExample = {
88
input: string
99
output: string
1010
}
@@ -15,11 +15,11 @@ export type FewShotExample = {
1515
export type AutoSelectType = "highly_rated" | "most_recent"
1616

1717
/**
18-
* Result of fetching few-shot examples from the dataset.
18+
* Result of fetching task sample examples from the dataset.
1919
*/
20-
export type FewShotFetchResult = {
20+
export type TaskSampleFetchResult = {
2121
auto_select_type: AutoSelectType | null
22-
selected_example: FewShotExample | null
22+
selected_example: TaskSampleExample | null
2323
available_runs: TaskRun[]
2424
}
2525

@@ -43,17 +43,17 @@ function get_output_string(run: TaskRun): string {
4343
}
4444

4545
/**
46-
* Converts a TaskRun to a FewShotExample.
46+
* Converts a TaskRun to a TaskSampleExample.
4747
*/
48-
export function task_run_to_example(run: TaskRun): FewShotExample {
48+
export function task_run_to_example(run: TaskRun): TaskSampleExample {
4949
return {
5050
input: run.input ?? "",
5151
output: get_output_string(run),
5252
}
5353
}
5454

5555
/**
56-
* Fetches task runs and determines the few-shot selection status.
56+
* Fetches task runs and determines the task sample selection status.
5757
*
5858
* Priority:
5959
* 1. If a 5-star rated sample exists, auto-select it (confident)
@@ -62,10 +62,10 @@ export function task_run_to_example(run: TaskRun): FewShotExample {
6262
*
6363
* @throws Error if fetching fails
6464
*/
65-
export async function fetch_few_shot_candidates(
65+
export async function fetch_task_sample_candidates(
6666
project_id: string,
6767
task_id: string,
68-
): Promise<FewShotFetchResult> {
68+
): Promise<TaskSampleFetchResult> {
6969
const { data: runs, error } = await client.GET(
7070
"/api/projects/{project_id}/tasks/{task_id}/runs",
7171
{
@@ -119,13 +119,13 @@ export async function fetch_few_shot_candidates(
119119
}
120120

121121
/**
122-
* Builds a task prompt with instruction, requirements, and optional few-shot examples.
122+
* Builds a task prompt with instruction, requirements, and optional task sample examples.
123123
* Uses the backend prompt builder for consistent formatting.
124124
*/
125-
export async function build_prompt_with_few_shot(
125+
export async function build_prompt_with_task_sample(
126126
project_id: string,
127127
task_id: string,
128-
examples: FewShotExample[],
128+
examples: TaskSampleExample[],
129129
): Promise<string> {
130130
const { data, error } = await client.POST(
131131
"/api/projects/{project_id}/tasks/{task_id}/build_prompt_with_examples",

app/web_ui/src/lib/utils/few_shot_selector.svelte renamed to app/web_ui/src/lib/utils/task_sample_selector.svelte

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22
import { onMount } from "svelte"
33
import type { TaskRun } from "$lib/types"
44
import {
5-
type FewShotExample,
5+
type TaskSampleExample,
66
type AutoSelectType,
7-
fetch_few_shot_candidates,
7+
fetch_task_sample_candidates,
88
task_run_to_example,
9-
} from "./few_shot_example"
9+
} from "./task_sample_example"
1010
import FormElement from "$lib/utils/form_element.svelte"
1111
import Output from "$lib/ui/output.svelte"
1212
import Collapse from "$lib/ui/collapse.svelte"
1313
import Dialog from "$lib/ui/dialog.svelte"
1414
1515
export let project_id: string
1616
export let task_id: string
17-
export let selected_example: FewShotExample | null = null
17+
export let selected_example: TaskSampleExample | null = null
1818
export let is_prompt_building: boolean = false
1919
export let has_unsaved_manual_entry: boolean = false
2020
@@ -53,6 +53,7 @@
5353
// Handle run selection
5454
function select_run(run: TaskRun) {
5555
selected_example = task_run_to_example(run)
56+
auto_select_type = null
5657
show_manual_entry = false
5758
is_changing_selection = false
5859
}
@@ -85,14 +86,15 @@
8586
input: manual_input.trim(),
8687
output: manual_output.trim(),
8788
}
89+
auto_select_type = null
8890
is_changing_selection = false
8991
show_manual_entry = true // keep this true so we know it was manual entry
9092
}
9193
}
9294
9395
onMount(async () => {
9496
try {
95-
const result = await fetch_few_shot_candidates(project_id, task_id)
97+
const result = await fetch_task_sample_candidates(project_id, task_id)
9698
auto_select_type = result.auto_select_type
9799
available_runs = result.available_runs
98100
@@ -152,7 +154,7 @@
152154
description="An example of the task running properly. It's used to help understand expected behaviour."
153155
open={user_verification_desired}
154156
>
155-
<div class="few-shot-selector">
157+
<div class="task-sample-selector">
156158
{#if loading}
157159
<div class="flex items-center gap-2 py-4">
158160
<div class="loading loading-spinner loading-sm"></div>

app/web_ui/src/routes/(app)/fine_tune/[project_id]/[task_id]/+page.svelte

Lines changed: 76 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -80,79 +80,81 @@
8080
}
8181
</script>
8282

83-
<AppPage
84-
title="Fine Tunes"
85-
subtitle="Fine-tune models for the current task."
86-
sub_subtitle="Read the Docs"
87-
sub_subtitle_link="https://docs.kiln.tech/docs/fine-tuning-guide"
88-
breadcrumbs={[
89-
{
90-
label: "Optimize",
91-
href: `/optimize/${project_id}/${task_id}`,
92-
},
93-
]}
94-
action_buttons={is_empty
95-
? []
96-
: [
97-
{
98-
label: "Create Fine Tune",
99-
href: `/fine_tune/${project_id}/${task_id}/create_finetune`,
100-
primary: true,
101-
},
102-
]}
103-
>
104-
{#if finetunes_loading}
105-
<div class="w-full min-h-[50vh] flex justify-center items-center">
106-
<div class="loading loading-spinner loading-lg"></div>
107-
</div>
108-
{:else if is_empty}
109-
<div class="flex flex-col items-center justify-center min-h-[60vh]">
110-
<EmptyFinetune {project_id} {task_id} />
111-
</div>
112-
{:else if finetunes}
113-
<div class="overflow-x-auto rounded-lg border">
114-
<table class="table">
115-
<thead>
116-
<tr>
117-
<th> Name </th>
118-
<th> Type </th>
119-
<th> Provider</th>
120-
<th> Base Model</th>
121-
<th> Status </th>
122-
<th> Created At </th>
123-
</tr>
124-
</thead>
125-
<tbody>
126-
{#each finetunes as finetune}
127-
<tr
128-
class="hover cursor-pointer"
129-
on:click={() => {
130-
goto(
131-
`/fine_tune/${project_id}/${task_id}/fine_tune/${finetune.id}`,
132-
)
133-
}}
134-
>
135-
<td> {finetune.name} </td>
136-
<td>
137-
{data_strategy_name(finetune.data_strategy)}
138-
</td>
139-
<td> {provider_name_from_id(finetune.provider)} </td>
140-
<td> {finetune.base_model_id} </td>
141-
<td> {format_status(finetune.latest_status)} </td>
142-
<td> {formatDate(finetune.created_at)} </td>
83+
<div class="max-w-[1400px]">
84+
<AppPage
85+
title="Fine Tunes"
86+
subtitle="Fine-tune models for the current task."
87+
sub_subtitle="Read the Docs"
88+
sub_subtitle_link="https://docs.kiln.tech/docs/fine-tuning-guide"
89+
breadcrumbs={[
90+
{
91+
label: "Optimize",
92+
href: `/optimize/${project_id}/${task_id}`,
93+
},
94+
]}
95+
action_buttons={is_empty
96+
? []
97+
: [
98+
{
99+
label: "Create Fine Tune",
100+
href: `/fine_tune/${project_id}/${task_id}/create_finetune`,
101+
primary: true,
102+
},
103+
]}
104+
>
105+
{#if finetunes_loading}
106+
<div class="w-full min-h-[50vh] flex justify-center items-center">
107+
<div class="loading loading-spinner loading-lg"></div>
108+
</div>
109+
{:else if is_empty}
110+
<div class="flex flex-col items-center justify-center min-h-[60vh]">
111+
<EmptyFinetune {project_id} {task_id} />
112+
</div>
113+
{:else if finetunes}
114+
<div class="overflow-x-auto rounded-lg border">
115+
<table class="table">
116+
<thead>
117+
<tr>
118+
<th> Name </th>
119+
<th> Type </th>
120+
<th> Provider</th>
121+
<th> Base Model</th>
122+
<th> Status </th>
123+
<th> Created At </th>
143124
</tr>
144-
{/each}
145-
</tbody>
146-
</table>
147-
</div>
148-
{:else if finetunes_error}
149-
<div
150-
class="w-full min-h-[50vh] flex flex-col justify-center items-center gap-2"
151-
>
152-
<div class="font-medium">Error Loading Fine Tunes</div>
153-
<div class="text-error text-sm">
154-
{finetunes_error.getMessage() || "An unknown error occurred"}
125+
</thead>
126+
<tbody>
127+
{#each finetunes as finetune}
128+
<tr
129+
class="hover cursor-pointer"
130+
on:click={() => {
131+
goto(
132+
`/fine_tune/${project_id}/${task_id}/fine_tune/${finetune.id}`,
133+
)
134+
}}
135+
>
136+
<td> {finetune.name} </td>
137+
<td>
138+
{data_strategy_name(finetune.data_strategy)}
139+
</td>
140+
<td> {provider_name_from_id(finetune.provider)} </td>
141+
<td> {finetune.base_model_id} </td>
142+
<td> {format_status(finetune.latest_status)} </td>
143+
<td> {formatDate(finetune.created_at)} </td>
144+
</tr>
145+
{/each}
146+
</tbody>
147+
</table>
148+
</div>
149+
{:else if finetunes_error}
150+
<div
151+
class="w-full min-h-[50vh] flex flex-col justify-center items-center gap-2"
152+
>
153+
<div class="font-medium">Error Loading Fine Tunes</div>
154+
<div class="text-error text-sm">
155+
{finetunes_error.getMessage() || "An unknown error occurred"}
156+
</div>
155157
</div>
156-
</div>
157-
{/if}
158-
</AppPage>
158+
{/if}
159+
</AppPage>
160+
</div>

0 commit comments

Comments
 (0)