Skip to content

Commit 96a87a6

Browse files
committed
support configuration of formulation timeout
1 parent 7f520c9 commit 96a87a6

File tree

6 files changed

+142
-25
lines changed

6 files changed

+142
-25
lines changed

py-src/data_formulator/app.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ def derive_data():
425425
new_fields = content["new_fields"]
426426
instruction = content["extra_prompt"]
427427

428+
max_repair_attempts = content["max_repair_attempts"] if "max_repair_attempts" in content else 1
429+
428430
if "additional_messages" in content:
429431
prev_messages = content["additional_messages"]
430432
else:
@@ -447,7 +449,7 @@ def derive_data():
447449
results = agent.run(input_tables, instruction, [field['name'] for field in new_fields], prev_messages)
448450

449451
repair_attempts = 0
450-
while results[0]['status'] == 'error' and repair_attempts == 0: # only try once
452+
while results[0]['status'] == 'error' and repair_attempts < max_repair_attempts: # try up to n times
451453
error_message = results[0]['content']
452454
new_instruction = f"We run into the following problem executing the code, please fix it:\n\n{error_message}\n\nPlease think step by step, reflect why the error happens and fix the code so that no more errors would occur."
453455

@@ -482,7 +484,10 @@ def refine_data():
482484
output_fields = content["output_fields"]
483485
dialog = content["dialog"]
484486
new_instruction = content["new_instruction"]
485-
487+
max_repair_attempts = content["max_repair_attempts"] if "max_repair_attempts" in content else 1
488+
489+
print("max_repair_attempts")
490+
print(max_repair_attempts)
486491
print("previous dialog")
487492
print(dialog)
488493

@@ -491,7 +496,7 @@ def refine_data():
491496
results = agent.followup(input_tables, dialog, [field['name'] for field in output_fields], new_instruction)
492497

493498
repair_attempts = 0
494-
while results[0]['status'] == 'error' and repair_attempts == 0: # only try once
499+
while results[0]['status'] == 'error' and repair_attempts < max_repair_attempts: # only try once
495500
error_message = results[0]['content']
496501
new_instruction = f"We run into the following problem executing the code, please fix it:\n\n{error_message}\n\nPlease think step by step, reflect why the error happens and fix the code so that no more errors would occur."
497502
prev_dialog = results[0]['dialog']

src/app/App.tsx

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
ToggleButton,
3636
Menu,
3737
MenuItem,
38+
TextField,
3839
} from '@mui/material';
3940

4041

@@ -46,7 +47,7 @@ import { DataFormulatorFC } from '../views/DataFormulator';
4647

4748
import GridViewIcon from '@mui/icons-material/GridView';
4849
import ViewSidebarIcon from '@mui/icons-material/ViewSidebar';
49-
50+
import SettingsIcon from '@mui/icons-material/Settings';
5051
import {
5152
createBrowserRouter,
5253
RouterProvider,
@@ -295,10 +296,101 @@ const ResetDialog: React.FC = () => {
295296
);
296297
};
297298

299+
const ConfigDialog: React.FC = () => {
300+
const [open, setOpen] = useState(false);
301+
const dispatch = useDispatch();
302+
const config = useSelector((state: DataFormulatorState) => state.config);
303+
304+
const [formulateTimeoutSeconds, setFormulateTimeoutSeconds] = useState(config.formulateTimeoutSeconds);
305+
const [maxRepairAttempts, setMaxRepairAttempts] = useState(config.maxRepairAttempts);
306+
307+
// Add check for changes
308+
const hasChanges = formulateTimeoutSeconds !== config.formulateTimeoutSeconds ||
309+
maxRepairAttempts !== config.maxRepairAttempts;
310+
311+
return (
312+
<>
313+
<Button variant="text" sx={{textTransform: 'none'}} onClick={() => setOpen(true)} startIcon={<SettingsIcon />}>
314+
<Box component="span" sx={{lineHeight: 1.2, display: 'flex', flexDirection: 'column', alignItems: 'left'}}>
315+
<Box component="span" sx={{py: 0, my: 0, fontSize: '10px', mr: 'auto'}}>timeout={config.formulateTimeoutSeconds}s</Box>
316+
<Box component="span" sx={{py: 0, my: 0, fontSize: '10px', mr: 'auto'}}>max_repair={config.maxRepairAttempts}</Box>
317+
</Box>
318+
</Button>
319+
<Dialog onClose={() => setOpen(false)} open={open}>
320+
<DialogTitle>Data Formulator Configuration</DialogTitle>
321+
<DialogContent>
322+
<DialogContentText>
323+
<Box sx={{
324+
display: 'flex',
325+
flexDirection: 'column',
326+
gap: 3,
327+
my: 2,
328+
maxWidth: 400
329+
}}>
330+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
331+
<Box sx={{ flex: 1 }}>
332+
<TextField
333+
label="formulate timeout (seconds)"
334+
type="number"
335+
variant="outlined"
336+
value={formulateTimeoutSeconds}
337+
onChange={(e) => setFormulateTimeoutSeconds(parseInt(e.target.value))}
338+
fullWidth
339+
/>
340+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
341+
Maximum time allowed for the formulation process before timing out.
342+
</Typography>
343+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
344+
Smaller values (&lt;30s) make the model fails fast thus providing a smoother UI experience. Increase this value for slow models.
345+
</Typography>
346+
</Box>
347+
</Box>
348+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
349+
<Box sx={{ flex: 1 }}>
350+
<TextField
351+
label="max repair attempts"
352+
type="number"
353+
variant="outlined"
354+
value={maxRepairAttempts}
355+
onChange={(e) => setMaxRepairAttempts(parseInt(e.target.value))}
356+
fullWidth
357+
/>
358+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
359+
Maximum number of times the LLM will attempt to repair code if generated code fails to execute (recommended = 1).
360+
</Typography>
361+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
362+
Higher values <span style={{fontStyle: 'italic'}}>might</span> increase chances of success but may take longer. Repair time is considered as part of the formulate timeout.
363+
</Typography>
364+
</Box>
365+
</Box>
366+
</Box>
367+
</DialogContentText>
368+
</DialogContent>
369+
<DialogActions sx={{'.MuiButton-root': {textTransform: 'none'}}}>
370+
<Button sx={{marginRight: 'auto'}} onClick={() => {
371+
setFormulateTimeoutSeconds(30);
372+
setMaxRepairAttempts(1);
373+
}}>Reset to default</Button>
374+
<Button onClick={() => setOpen(false)}>Cancel</Button>
375+
<Button
376+
variant={hasChanges ? "contained" : "text"}
377+
onClick={() => {
378+
dispatch(dfActions.setConfig({formulateTimeoutSeconds, maxRepairAttempts}));
379+
setOpen(false);
380+
}}
381+
>
382+
Apply
383+
</Button>
384+
</DialogActions>
385+
</Dialog>
386+
</>
387+
);
388+
}
389+
298390
export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
299391

300392
const visViewMode = useSelector((state: DataFormulatorState) => state.visViewMode);
301-
const betaMode = useSelector((state: DataFormulatorState) => state.betaMode);
393+
const config = useSelector((state: DataFormulatorState) => state.config);
302394
const tables = useSelector((state: DataFormulatorState) => state.tables);
303395

304396
// if the user has logged in
@@ -410,7 +502,7 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
410502

411503
let appBar = [
412504
<AppBar className="app-bar" position="static" key="app-bar-main">
413-
<Toolbar variant="dense" sx={{ backgroundColor: betaMode ? 'lavender' : '' }}>
505+
<Toolbar variant="dense">
414506
<Button href={"/"} sx={{
415507
display: "flex", flexDirection: "row", textTransform: "none",
416508
backgroundColor: 'transparent',
@@ -420,7 +512,7 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
420512
}} color="inherit">
421513
<Box component="img" sx={{ height: 32, marginRight: "12px" }} alt="" src={dfLogo} />
422514
<Typography variant="h6" noWrap component="h1" sx={{ fontWeight: 300, display: { xs: 'none', sm: 'block' } }}>
423-
{toolName} {betaMode ? "β" : ""} {process.env.NODE_ENV == "development" ? "" : ""}
515+
{toolName} {process.env.NODE_ENV == "development" ? "" : ""}
424516
</Typography>
425517
</Button>
426518
<Box sx={{ flexGrow: 1, textAlign: 'center', display: 'flex', justifyContent: 'center' }} >
@@ -432,6 +524,8 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
432524
about
433525
</Button>
434526
<Divider orientation="vertical" variant="middle" flexItem /> */}
527+
<ConfigDialog />
528+
<Divider orientation="vertical" variant="middle" flexItem />
435529
<ModelSelectionButton />
436530
<Divider orientation="vertical" variant="middle" flexItem />
437531
<Typography sx={{ display: 'flex', fontSize: 14, alignItems: 'center', gap: 1 }}>

src/app/dfSlice.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ export interface DataFormulatorState {
6565

6666
chartSynthesisInProgress: string[];
6767

68-
betaMode: boolean;
68+
config: {
69+
formulateTimeoutSeconds: number;
70+
maxRepairAttempts: number;
71+
}
6972
}
7073

7174
// Define the initial state using that type
@@ -98,7 +101,10 @@ const initialState: DataFormulatorState = {
98101

99102
chartSynthesisInProgress: [],
100103

101-
betaMode: false,
104+
config: {
105+
formulateTimeoutSeconds: 30,
106+
maxRepairAttempts: 1,
107+
}
102108
}
103109

104110
let getUnrefedDerivedTableIds = (state: DataFormulatorState) => {
@@ -247,7 +253,11 @@ export const dataFormulatorSlice = createSlice({
247253

248254
state.chartSynthesisInProgress = [];
249255

250-
state.betaMode = false;
256+
// avoid resetting config
257+
// state.config = {
258+
// formulateTimeoutSeconds: 30,
259+
// repairAttempts: 1,
260+
// }
251261
},
252262
loadState: (state, action: PayloadAction<any>) => {
253263

@@ -274,10 +284,10 @@ export const dataFormulatorSlice = createSlice({
274284

275285
state.chartSynthesisInProgress = [];
276286

277-
state.betaMode = savedState.betaMode;
287+
state.config = savedState.config;
278288
},
279-
toggleBetaMode: (state, action: PayloadAction<boolean>) => {
280-
state.betaMode = action.payload;
289+
setConfig: (state, action: PayloadAction<{formulateTimeoutSeconds: number, maxRepairAttempts: number}>) => {
290+
state.config = action.payload;
281291
},
282292
selectModel: (state, action: PayloadAction<string | undefined>) => {
283293
state.selectedModelId = action.payload;

src/views/EncodingShelfCard.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ export const EncodingShelfCard: FC<EncodingShelfCardProps> = function ({ chartId
308308
// reference to states
309309
const tables = useSelector((state: DataFormulatorState) => state.tables);
310310
const charts = useSelector((state: DataFormulatorState) => state.charts);
311+
const config = useSelector((state: DataFormulatorState) => state.config);
311312

312313
let existMultiplePossibleBaseTables = tables.filter(t => t.derive == undefined).length > 1;
313314

@@ -421,7 +422,8 @@ export const EncodingShelfCard: FC<EncodingShelfCardProps> = function ({ chartId
421422
input_tables: actionTables.map(t => {return { name: t.id.replace(/\.[^/.]+$/ , ""), rows: t.rows }}),
422423
new_fields: activeBaseFields.map(f => { return {name: f.name} }),
423424
extra_prompt: instruction,
424-
model: activeModel
425+
model: activeModel,
426+
max_repair_attempts: config.maxRepairAttempts
425427
})
426428
let engine = getUrls().SERVER_DERIVE_DATA_URL;
427429

@@ -449,7 +451,8 @@ export const EncodingShelfCard: FC<EncodingShelfCardProps> = function ({ chartId
449451
new_fields: activeBaseFields.map(f => { return {name: f.name} }),
450452
extra_prompt: instruction,
451453
model: activeModel,
452-
additional_messages: additionalMessages
454+
additional_messages: additionalMessages,
455+
max_repair_attempts: config.maxRepairAttempts
453456
});
454457
engine = getUrls().SERVER_DERIVE_DATA_URL;
455458
} else {
@@ -460,7 +463,8 @@ export const EncodingShelfCard: FC<EncodingShelfCardProps> = function ({ chartId
460463
output_fields: activeBaseFields.map(f => { return {name: f.name} }),
461464
dialog: currentTable.derive?.dialog,
462465
new_instruction: instruction,
463-
model: activeModel
466+
model: activeModel,
467+
max_repair_attempts: config.maxRepairAttempts
464468
})
465469
engine = getUrls().SERVER_REFINE_DATA_URL;
466470
}
@@ -479,7 +483,7 @@ export const EncodingShelfCard: FC<EncodingShelfCardProps> = function ({ chartId
479483

480484
// timeout the request after 30 seconds
481485
const controller = new AbortController();
482-
const timeoutId = setTimeout(() => controller.abort(), 30000);
486+
const timeoutId = setTimeout(() => controller.abort(), config.formulateTimeoutSeconds * 1000);
483487

484488
fetch(engine, {...message, signal: controller.signal })
485489
.then((response) => response.json())
@@ -650,7 +654,7 @@ export const EncodingShelfCard: FC<EncodingShelfCardProps> = function ({ chartId
650654
dispatch(dfActions.addMessages({
651655
"timestamp": Date.now(),
652656
"type": "error",
653-
"value": "Data formulation timed out after 30 seconds. Please try again.",
657+
"value": `Data formulation timed out after ${config.formulateTimeoutSeconds} seconds. Consider breaking down the task, using a different model or prompt, or increasing the timeout limit.`,
654658
"detail": "Request exceeded timeout limit"
655659
}));
656660
} else {

src/views/EncodingShelfThread.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ export const EncodingShelfThread: FC<EncodingShelfThreadProps> = function ({ cha
155155
const charts = useSelector((state: DataFormulatorState) => state.charts);
156156
let activeThreadChartId = useSelector((state: DataFormulatorState) => state.activeThreadChartId);
157157
let activeModel = useSelector(dfSelectors.getActiveModel);
158-
158+
const config = useSelector((state: DataFormulatorState) => state.config);
159+
159160
let [reformulateRunning, setReformulteRunning] = useState<boolean>(false);
160161

161162
let activeThreadChart = charts.find(c => c.id == activeThreadChartId);
@@ -215,7 +216,8 @@ export const EncodingShelfThread: FC<EncodingShelfThreadProps> = function ({ cha
215216
input_tables: baseTables.map(t => {return { name: t.id.replace(/\.[^/.]+$/ , ""), rows: t.rows }}),
216217
new_fields: activeBaseFields.map(f => { return {name: f.name} }),
217218
extra_prompt: prompt,
218-
model: activeModel
219+
model: activeModel,
220+
max_repair_attempts: config.maxRepairAttempts
219221
})
220222
let engine = getUrls().SERVER_DERIVE_DATA_URL;
221223

@@ -231,7 +233,8 @@ export const EncodingShelfThread: FC<EncodingShelfThreadProps> = function ({ cha
231233
new_fields: activeBaseFields.map(f => { return {name: f.name} }),
232234
extra_prompt: prompt,
233235
additional_messages: triggerTable.derive?.dialog,
234-
model: activeModel
236+
model: activeModel,
237+
max_repair_attempts: config.maxRepairAttempts
235238
})
236239
engine = getUrls().SERVER_DERIVE_DATA_URL;
237240
// messageBody = JSON.stringify({
@@ -241,7 +244,8 @@ export const EncodingShelfThread: FC<EncodingShelfThreadProps> = function ({ cha
241244
// output_fields: activeBaseFields.map(f => { return {name: f.name } }),
242245
// dialog: triggerTable.derive?.dialog,
243246
// new_instruction: prompt,
244-
// model: activeModel
247+
// model: activeModel,
248+
// max_repair_attempts: config.maxRepairAttempts
245249
// })
246250
//engine = getUrls().SERVER_REFINE_DATA_URL;
247251
}
@@ -258,7 +262,7 @@ export const EncodingShelfThread: FC<EncodingShelfThreadProps> = function ({ cha
258262

259263
// timeout the request after 30 seconds
260264
const controller = new AbortController();
261-
const timeoutId = setTimeout(() => controller.abort(), 30000);
265+
const timeoutId = setTimeout(() => controller.abort(), config.formulateTimeoutSeconds * 1000);
262266

263267
setReformulteRunning(true);
264268

@@ -386,7 +390,7 @@ export const EncodingShelfThread: FC<EncodingShelfThreadProps> = function ({ cha
386390
dispatch(dfActions.addMessages({
387391
"timestamp": Date.now(),
388392
"type": "error",
389-
"value": "Data formulation timed out after 30 seconds. Please try again.",
393+
"value": `Data formulation timed out after ${config.formulateTimeoutSeconds} seconds. Consider breaking down the task, using a different model or prompt, or increasing the timeout limit.`,
390394
"detail": "Request exceeded timeout limit"
391395
}));
392396
} else {

src/views/ModelSelectionDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ export const ModelSelectionButton: React.FC<{}> = ({ }) => {
436436

437437
return <>
438438
<Tooltip title="select model">
439-
<Button sx={{fontSize: "inherit", textTransform: "none"}} variant="text" color="primary" onClick={()=>{setModelDialogOpen(true)}} endIcon={selectedModelId ? <SettingsIcon /> : ''}>
439+
<Button sx={{fontSize: "inherit", textTransform: "none"}} variant="text" color="primary" onClick={()=>{setModelDialogOpen(true)}}>
440440
{selectedModelId ? `Model: ${(models.find(m => m.id == selectedModelId) as any)?.model}` : 'Select A Model'}
441441
</Button>
442442
</Tooltip>

0 commit comments

Comments
 (0)