Skip to content

Commit a70e484

Browse files
authored
Merge pull request #195 from microsoft/mitokic/12082025/agent-iterate-updates
add new finalize_run step to iterate forecast process
2 parents c8cfb58 + 98686b6 commit a70e484

File tree

4 files changed

+168
-13
lines changed

4 files changed

+168
-13
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: finnts
22
Title: Microsoft Finance Time Series Forecasting Framework
3-
Version: 0.6.0.9013
3+
Version: 0.6.0.9014
44
Authors@R:
55
c(person(given = "Mike",
66
family = "Tokic",

NEWS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# finnts 0.6.0.9013 (development version)
1+
# finnts 0.6.0.9014 (development version)
22

33
## Improvements
44
- TimeGPT Integration

R/agent_iterate_forecast.R

Lines changed: 164 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ iterate_forecast <- function(agent_info,
8484
agent_info$project_info$combo_variables <- "ID"
8585
}
8686

87+
agent_info$max_iter <- max_iter
88+
8789
# run exploratory data analysis
8890
# check if eda data already exists
8991
eda_exists <- tryCatch(
@@ -160,23 +162,60 @@ iterate_forecast <- function(agent_info,
160162
} else {
161163
cli::cli_alert_info("Max iterations already met. Skipping global model optimization.")
162164
}
165+
} else if (length(combo_list) > 1 & !run_global_models) {
166+
message("[agent] Global models disabled. Skipping global model optimization.")
167+
} else {
168+
message("[agent] Only one time series found. Skipping global model optimization.")
169+
}
170+
171+
# get local combo list
172+
best_run_tbl <- load_best_agent_run(agent_info = agent_info)
173+
174+
if (nrow(best_run_tbl) > 0) {
175+
# check if max_iter OR run_complete columns exist (for backward compatibility)
176+
if (!"run_complete" %in% colnames(best_run_tbl) | !"max_iterations" %in% colnames(best_run_tbl)) {
177+
best_run_tbl <- best_run_tbl %>%
178+
dplyr::mutate(
179+
run_complete = FALSE,
180+
max_iterations = 0
181+
)
182+
}
183+
184+
# set run_complete to FALSE for global models that did not meet mape goal,
185+
# which will allow local models to run if needed
186+
best_run_tbl <- best_run_tbl %>%
187+
dplyr::mutate(
188+
run_complete = ifelse(model_type == "global" & weighted_mape > weighted_mape_goal, FALSE, run_complete)
189+
)
190+
191+
# add in missing combos that have not been run yet (no agent best run file)
192+
missing_combos <- setdiff(combo_list, best_run_tbl$combo)
193+
194+
if (length(missing_combos) > 0) {
195+
missing_tbl <- tibble::tibble(
196+
combo = missing_combos,
197+
weighted_mape = Inf,
198+
run_complete = FALSE,
199+
max_iterations = 0
200+
)
201+
202+
best_run_tbl <- best_run_tbl %>%
203+
dplyr::bind_rows(missing_tbl)
204+
}
163205

164-
# filter out which time series met the mape goal after global models
165-
local_combo_list <- load_best_agent_run(agent_info = agent_info) %>%
206+
# filter combos that need local model optimization
207+
local_combo_list <- best_run_tbl %>%
166208
dplyr::filter(weighted_mape > weighted_mape_goal) %>%
209+
dplyr::filter(run_complete == FALSE | max_iterations < max_iter) %>%
167210
dplyr::pull(combo) %>%
168211
unique()
169-
} else if (length(combo_list) > 1 & !run_global_models) {
170-
message("[agent] Global models disabled. Skipping global model optimization.")
171-
local_combo_list <- combo_list
172212
} else {
173-
message("[agent] Only one time series found. Skipping global model optimization.")
174213
local_combo_list <- combo_list
175214
}
176215

177216
# optimize local models
178217
if (length(local_combo_list) == 0) {
179-
message("[agent] All time series met the MAPE goal after global models. Skipping local model optimization.")
218+
message("[agent] All time series already finished running. Skipping local model optimization.")
180219
} else if (!run_local_models) {
181220
message("[agent] Local models disabled. Skipping local model optimization.")
182221
} else {
@@ -752,6 +791,10 @@ save_best_agent_run <- function(agent_info) {
752791
stop("Error in save_best_agent_run(). No best run found for agent.", call. = FALSE)
753792
}
754793

794+
# remove unnecessary columns
795+
final_run_tbl <- final_run_tbl %>%
796+
dplyr::select(-dplyr::any_of(c("run_complete", "max_iterations")))
797+
755798
# save the best run for the agent
756799
write_data(
757800
x = final_run_tbl,
@@ -822,7 +865,14 @@ fcst_agent_workflow <- function(agent_info,
822865

823866
# check if the LLM aborted the run
824867
if ("abort" %in% names(results$reason_inputs) && results$reason_inputs$abort == "TRUE") {
825-
return(list(ctx = ctx, `next` = "stop"))
868+
# check if any iterations have been completed
869+
if (ctx$iter > 0) {
870+
# proceed to finalize run step if there is existing run_info from previous iterations
871+
return(list(ctx = ctx, `next` = "finalize_run"))
872+
} else {
873+
# skip finalize run step since run was aborted with no existing run_info
874+
return(list(ctx = ctx, `next` = "stop"))
875+
}
826876
} else {
827877
return(list(ctx = ctx, `next` = "submit_fcst_run"))
828878
}
@@ -903,14 +953,25 @@ fcst_agent_workflow <- function(agent_info,
903953

904954
# determine next node based on conditions
905955
if (wmape_goal_reached || max_runs_reached) {
906-
next_node <- "stop"
956+
next_node <- "finalize_run"
907957
} else {
908958
next_node <- "start"
909959
}
910960

911961
return(list(ctx = ctx, `next` = next_node))
912962
}
913963
),
964+
finalize_run = list(
965+
fn = "finalize_run",
966+
`next` = "stop",
967+
retry_mode = "plain",
968+
max_retry = 3,
969+
args = list(
970+
agent_info = agent_info,
971+
run_info = "{results$submit_fcst_run}",
972+
combo = combo
973+
)
974+
),
914975
stop = list(fn = NULL)
915976
)
916977

@@ -1632,7 +1693,9 @@ log_best_run <- function(agent_info,
16321693
best_run_name = run_info$run_name,
16331694
model_type = ifelse(combo == "all", "global", "local"),
16341695
combo = combo_name,
1635-
weighted_mape = wmape
1696+
weighted_mape = wmape,
1697+
max_iterations = 0,
1698+
run_complete = FALSE
16361699
) %>%
16371700
dplyr::left_join(
16381701
log_df %>%
@@ -1656,6 +1719,97 @@ log_best_run <- function(agent_info,
16561719
return("Run logged successfully.")
16571720
}
16581721

1722+
#' Finalize Agent Run Metadata
1723+
#'
1724+
#' This function updates the agent best run file for each combo by setting
1725+
#' the max_iterations and run_complete flags. For global models (combo = NULL or combo = "all"),
1726+
#' it updates all combo files. For local models, it updates a single combo file.
1727+
#'
1728+
#' @param agent_info Agent info from `set_agent_info()`
1729+
#' @param run_info A list containing run information including project name, run name, storage object, path, data output, and object output.
1730+
#' @param combo A character string representing the hashed combo. If NULL or "all", updates all combos for global models.
1731+
#'
1732+
#' @return Character string indicating success
1733+
#' @noRd
1734+
finalize_run <- function(agent_info,
1735+
run_info,
1736+
combo = NULL) {
1737+
# metadata
1738+
project_info <- agent_info$project_info
1739+
project_info$run_name <- agent_info$run_id
1740+
max_iter <- agent_info$max_iter
1741+
1742+
# load forecast to get combo list (always load to get original combo names)
1743+
if (is.null(combo)) {
1744+
combo <- "all"
1745+
combo_filter <- "All-Data"
1746+
} else {
1747+
combo_filter <- combo
1748+
}
1749+
1750+
back_test_tbl <- load_combo_forecast(
1751+
combo = combo_filter,
1752+
run_info = run_info
1753+
)
1754+
1755+
# get unique combo names (unhashed)
1756+
combo_list <- unique(back_test_tbl$Combo)
1757+
1758+
# for each combo, update the agent best run file
1759+
for (combo_name in combo_list) {
1760+
# hash the combo name for file operations
1761+
combo_hash <- hash_data(combo_name)
1762+
1763+
# load the current best run file for this combo
1764+
best_run_file <- paste0(
1765+
project_info$path, "/logs/",
1766+
hash_data(project_info$project_name), "-",
1767+
hash_data(agent_info$run_id), "-",
1768+
combo_hash, "-agent_best_run.", project_info$data_output
1769+
) %>% fs::path_tidy()
1770+
1771+
best_run_tbl <- read_file(
1772+
run_info = project_info,
1773+
file_list = best_run_file,
1774+
return_type = "df"
1775+
)
1776+
1777+
if (nrow(best_run_tbl) == 0) {
1778+
stop(
1779+
paste0(
1780+
"Error in finalize_run(). No best run file found for combo: ",
1781+
combo_name
1782+
),
1783+
call. = FALSE
1784+
)
1785+
}
1786+
1787+
# skip updating for global runs that are less accurate than previous local best runs
1788+
if (best_run_tbl$model_type == "local" & combo == "all") {
1789+
next
1790+
}
1791+
1792+
# update max_iterations and run_complete
1793+
best_run_tbl_updated <- best_run_tbl %>%
1794+
dplyr::mutate(
1795+
max_iterations = max_iter,
1796+
run_complete = TRUE
1797+
)
1798+
1799+
# write the updated file back using the original combo name
1800+
write_data(
1801+
x = best_run_tbl_updated,
1802+
combo = combo_name,
1803+
run_info = project_info,
1804+
output_type = "log",
1805+
folder = "logs",
1806+
suffix = "-agent_best_run"
1807+
)
1808+
}
1809+
1810+
return("Run finalized successfully.")
1811+
}
1812+
16591813
#' Load previous run results for the agent
16601814
#'
16611815
#' @param agent_info A list containing agent information including project info and run ID.

R/utility.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ utils::globalVariables(c(
2727
"rolling_window_periods", "run_name", "run_number", "stationary_adf", "stationary_kpss",
2828
"to", "total_rows", "weighted_mape", "Analysis_Type", "Metric", "Value_Numeric",
2929
"is_stationary", "outlier_pct", "model_class", "section", "value", "Hierarchy_Level",
30-
"Sort_Order", "run_id", "date_type", "file_path", "models_to_run", "underscore_count"
30+
"Sort_Order", "run_id", "date_type", "file_path", "models_to_run", "underscore_count",
31+
"max_iterations", "run_complete"
3132
))
3233

3334
#' @importFrom magrittr %>%

0 commit comments

Comments
 (0)