Skip to content

Commit 6e91164

Browse files
authored
[deploy] 0.1.6.1 support setting timeouts
Dev: provide ability to set timeout options, and improved backend logging so that users can provide feedback
2 parents c0becaa + 81a3cc0 commit 6e91164

File tree

9 files changed

+199
-46
lines changed

9 files changed

+199
-46
lines changed

py-src/data_formulator/agents/agent_data_rec.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ def process_gpt_response(self, input_tables, messages, response):
181181
result['refined_goal'] = refined_goal
182182
candidates.append(result)
183183

184+
logger.info("=== Recommendation Candidates ===>")
185+
for candidate in candidates:
186+
for key, value in candidate.items():
187+
if key in ['dialog', 'content']:
188+
logger.info(f"##{key}:\n{str(value)[:1000]}...")
189+
else:
190+
logger.info(f"## {key}:\n{value}")
191+
184192
return candidates
185193

186194

py-src/data_formulator/agents/agent_data_transform_v2.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,16 +224,9 @@ def process_gpt_response(self, input_tables, messages, response):
224224

225225
code_blocks = extract_code_from_gpt_response(choice.message.content + "\n", "python")
226226

227-
logger.info("=== Code blocks ===>")
228-
logger.info(code_blocks)
229-
230227
if len(code_blocks) > 0:
231228
code_str = code_blocks[-1]
232229

233-
for table in input_tables:
234-
logger.info(f"Table: {table['name']}")
235-
logger.info(table['rows'])
236-
237230
try:
238231
result = py_sandbox.run_transform_in_sandbox2020(code_str, [t['rows'] for t in input_tables])
239232
result['code'] = code_str
@@ -256,8 +249,13 @@ def process_gpt_response(self, input_tables, messages, response):
256249
result['refined_goal'] = refined_goal
257250
candidates.append(result)
258251

259-
logger.info("=== Candidates ===>")
260-
logger.info(candidates)
252+
logger.info("=== Transform Candidates ===>")
253+
for candidate in candidates:
254+
for key, value in candidate.items():
255+
if key in ['dialog', 'content']:
256+
logger.info(f"##{key}:\n{str(value)[:1000]}...")
257+
else:
258+
logger.info(f"## {key}:\n{value}")
261259

262260
return candidates
263261

py-src/data_formulator/app.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ def test_model():
213213
content = request.get_json()
214214

215215
# contains endpoint, key, model, api_base, api_version
216-
print("content------------------------------")
217-
print(content)
216+
logger.info("content------------------------------")
217+
logger.info(content)
218218

219219
client = get_client(content['model'])
220220

@@ -226,8 +226,8 @@ def test_model():
226226
]
227227
)
228228

229-
print(f"model: {content['model']}")
230-
print(f"welcome message: {response.choices[0].message.content}")
229+
logger.info(f"model: {content['model']}")
230+
logger.info(f"welcome message: {response.choices[0].message.content}")
231231

232232
if "I can hear you." in response.choices[0].message.content:
233233
result = {
@@ -236,7 +236,7 @@ def test_model():
236236
"message": ""
237237
}
238238
except Exception as e:
239-
print(f"Error: {e}")
239+
logger.info(f"Error: {e}")
240240
error_message = str(e)
241241
result = {
242242
"model": content['model'],
@@ -250,13 +250,13 @@ def test_model():
250250

251251
@app.route("/", defaults={"path": ""})
252252
def index_alt(path):
253-
print(app.static_folder)
253+
logger.info(app.static_folder)
254254
return send_from_directory(app.static_folder, "index.html")
255255

256256
@app.errorhandler(404)
257257
def page_not_found(e):
258258
# your processing here
259-
print(app.static_folder)
259+
logger.info(app.static_folder)
260260
return send_from_directory(app.static_folder, "index.html") #'Hello 404!' #send_from_directory(app.static_folder, "index.html")
261261

262262
###### test functions ######
@@ -425,14 +425,21 @@ 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:
431433
prev_messages = []
432434

433-
print("spec------------------------------")
434-
print(new_fields)
435-
print(instruction)
435+
logger.info("== input tables ===>")
436+
for table in input_tables:
437+
logger.info(f"===> Table: {table['name']} (first 5 rows)")
438+
logger.info(table['rows'][:5])
439+
440+
logger.info("== user spec ===")
441+
logger.info(new_fields)
442+
logger.info(instruction)
436443

437444
mode = "transform"
438445
if len(new_fields) == 0:
@@ -447,7 +454,7 @@ def derive_data():
447454
results = agent.run(input_tables, instruction, [field['name'] for field in new_fields], prev_messages)
448455

449456
repair_attempts = 0
450-
while results[0]['status'] == 'error' and repair_attempts == 0: # only try once
457+
while results[0]['status'] == 'error' and repair_attempts < max_repair_attempts: # try up to n times
451458
error_message = results[0]['content']
452459
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."
453460

@@ -482,16 +489,23 @@ def refine_data():
482489
output_fields = content["output_fields"]
483490
dialog = content["dialog"]
484491
new_instruction = content["new_instruction"]
492+
max_repair_attempts = content["max_repair_attempts"] if "max_repair_attempts" in content else 1
493+
494+
logger.info("== input tables ===>")
495+
for table in input_tables:
496+
logger.info(f"===> Table: {table['name']} (first 5 rows)")
497+
logger.info(table['rows'][:5])
485498

486-
print("previous dialog")
487-
print(dialog)
499+
logger.info("== user spec ===>")
500+
logger.info(output_fields)
501+
logger.info(new_instruction)
488502

489503
# always resort to the data transform agent
490504
agent = DataTransformationAgentV2(client=client)
491505
results = agent.followup(input_tables, dialog, [field['name'] for field in output_fields], new_instruction)
492506

493507
repair_attempts = 0
494-
while results[0]['status'] == 'error' and repair_attempts == 0: # only try once
508+
while results[0]['status'] == 'error' and repair_attempts < max_repair_attempts: # only try once
495509
error_message = results[0]['content']
496510
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."
497511
prev_dialog = results[0]['dialog']

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "data_formulator"
7-
version = "0.1.6"
7+
version = "0.1.6.1"
88

99
requires-python = ">=3.9"
1010
authors = [

src/app/App.tsx

Lines changed: 119 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,122 @@ 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) => {
338+
const value = parseInt(e.target.value);
339+
setFormulateTimeoutSeconds(value);
340+
}}
341+
inputProps={{
342+
min: 0,
343+
max: 3600,
344+
}}
345+
error={formulateTimeoutSeconds <= 0 || formulateTimeoutSeconds > 3600}
346+
helperText={formulateTimeoutSeconds <= 0 || formulateTimeoutSeconds > 3600 ?
347+
"Value must be between 1 and 3600 seconds" : ""}
348+
fullWidth
349+
/>
350+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
351+
Maximum time allowed for the formulation process before timing out.
352+
</Typography>
353+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
354+
Smaller values (&lt;30s) make the model fails fast thus providing a smoother UI experience. Increase this value for slow models.
355+
</Typography>
356+
</Box>
357+
</Box>
358+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
359+
<Box sx={{ flex: 1 }}>
360+
<TextField
361+
label="max repair attempts"
362+
type="number"
363+
variant="outlined"
364+
value={maxRepairAttempts}
365+
onChange={(e) => {
366+
const value = parseInt(e.target.value);
367+
setMaxRepairAttempts(value);
368+
}}
369+
fullWidth
370+
inputProps={{
371+
min: 1,
372+
max: 5,
373+
}}
374+
error={maxRepairAttempts <= 0 || maxRepairAttempts > 5}
375+
helperText={maxRepairAttempts <= 0 || maxRepairAttempts > 5 ?
376+
"Value must be between 1 and 5" : ""}
377+
/>
378+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
379+
Maximum number of times the LLM will attempt to repair code if generated code fails to execute (recommended = 1).
380+
</Typography>
381+
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
382+
Higher values might slightly increase the chance of success but can crash the backend. Repair time is as part of the formulate timeout.
383+
</Typography>
384+
</Box>
385+
</Box>
386+
</Box>
387+
</DialogContentText>
388+
</DialogContent>
389+
<DialogActions sx={{'.MuiButton-root': {textTransform: 'none'}}}>
390+
<Button sx={{marginRight: 'auto'}} onClick={() => {
391+
setFormulateTimeoutSeconds(30);
392+
setMaxRepairAttempts(1);
393+
}}>Reset to default</Button>
394+
<Button onClick={() => setOpen(false)}>Cancel</Button>
395+
<Button
396+
variant={hasChanges ? "contained" : "text"}
397+
disabled={!hasChanges || isNaN(maxRepairAttempts) || maxRepairAttempts <= 0 || maxRepairAttempts > 5 || isNaN(formulateTimeoutSeconds) || formulateTimeoutSeconds <= 0 || formulateTimeoutSeconds > 3600}
398+
onClick={() => {
399+
dispatch(dfActions.setConfig({formulateTimeoutSeconds, maxRepairAttempts}));
400+
setOpen(false);
401+
}}
402+
>
403+
Apply
404+
</Button>
405+
</DialogActions>
406+
</Dialog>
407+
</>
408+
);
409+
}
410+
298411
export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
299412

300413
const visViewMode = useSelector((state: DataFormulatorState) => state.visViewMode);
301-
const betaMode = useSelector((state: DataFormulatorState) => state.betaMode);
414+
const config = useSelector((state: DataFormulatorState) => state.config);
302415
const tables = useSelector((state: DataFormulatorState) => state.tables);
303416

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

411524
let appBar = [
412525
<AppBar className="app-bar" position="static" key="app-bar-main">
413-
<Toolbar variant="dense" sx={{ backgroundColor: betaMode ? 'lavender' : '' }}>
526+
<Toolbar variant="dense">
414527
<Button href={"/"} sx={{
415528
display: "flex", flexDirection: "row", textTransform: "none",
416529
backgroundColor: 'transparent',
@@ -420,7 +533,7 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
420533
}} color="inherit">
421534
<Box component="img" sx={{ height: 32, marginRight: "12px" }} alt="" src={dfLogo} />
422535
<Typography variant="h6" noWrap component="h1" sx={{ fontWeight: 300, display: { xs: 'none', sm: 'block' } }}>
423-
{toolName} {betaMode ? "β" : ""} {process.env.NODE_ENV == "development" ? "" : ""}
536+
{toolName} {process.env.NODE_ENV == "development" ? "" : ""}
424537
</Typography>
425538
</Button>
426539
<Box sx={{ flexGrow: 1, textAlign: 'center', display: 'flex', justifyContent: 'center' }} >
@@ -432,6 +545,8 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
432545
about
433546
</Button>
434547
<Divider orientation="vertical" variant="middle" flexItem /> */}
548+
<ConfigDialog />
549+
<Divider orientation="vertical" variant="middle" flexItem />
435550
<ModelSelectionButton />
436551
<Divider orientation="vertical" variant="middle" flexItem />
437552
<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;

0 commit comments

Comments
 (0)