diff --git a/R/patientChat.R b/R/patientChat.R index 2312935..b43d391 100644 --- a/R/patientChat.R +++ b/R/patientChat.R @@ -57,13 +57,14 @@ patientChat <- R6::R6Class( #' #' @return A new `Person` object. initialize = function(system_prompt = NULL, + provider = "openai", model = "gpt-5.4", jsonSchemaPath = NULL, echo = c("none", "output", "all"), codelist_data = NULL) { # Check API and available models ----------------------- - private$.api_check(model) + # private$.api_check(model) # check JSON schema file ------------------------------- private$.json_schema_check(jsonSchemaPath) @@ -76,13 +77,13 @@ patientChat <- R6::R6Class( must exclusively conform to the OMOP-CDM v5.4 standard" } - # Create chat ------------------------------------------ - self$chat <- ellmer::chat_openai( - system_prompt = system_prompt, - model = model, - echo = echo + # Select chat provider ------------------------------------------ + private$.selectProvider( + provider, + system_prompt, + model, + echo ) - cli::cli_alert_success("Chat created") # Codelist --------------------------------------------- if (!is.null(codelist_data)) { @@ -229,6 +230,33 @@ patientChat <- R6::R6Class( ), private = list( + + .selectProvider = function(provider, + system_prompt, + model, + echo) { + + if (provider == "openai") { + self$chat <- ellmer::chat_openai( + system_prompt = system_prompt, + model = model, + echo = echo + ) + cli::cli_alert_success("openai chat created") + } else if (provider == "ollama") { + self$chat <- ellmer::chat_ollama( + system_prompt = system_prompt, + model = model, + echo = echo + ) + cli::cli_alert_success( + glue::glue( + "ollama chat created with {model}" + ) + ) + } + + }, .api_check = function(model) { checkmate::assertCharacter(model) diff --git a/man/patientChat.Rd b/man/patientChat.Rd index 342eb6d..b8dfe6d 100644 --- a/man/patientChat.Rd +++ b/man/patientChat.Rd @@ -64,6 +64,7 @@ generator$save("my_test") \if{html}{\out{
}} \preformatted{patientChat$new( system_prompt = NULL, + provider = "openai", model = "gpt-5.4", jsonSchemaPath = NULL, echo = c("none", "output", "all"), diff --git a/tests/testthat/helper-api.R b/tests/testthat/helper-api.R index d1a3489..17766ed 100644 --- a/tests/testthat/helper-api.R +++ b/tests/testthat/helper-api.R @@ -6,6 +6,14 @@ skip_if_no_openai <- function() { } } +skip_if_no_ollama <- function() { + testthat::skip_on_cran() + key <- Sys.getenv("OPENAI_API_KEY", unset = "") + if (!nzchar(key)) { + testthat::skip("OPENAI_API_KEY not set") + } +} + pick_openai_model <- function() { models <- tryCatch( PatientGenerator::availableModels(), diff --git a/tests/testthat/test-ollama.R b/tests/testthat/test-ollama.R new file mode 100644 index 0000000..6bcd115 --- /dev/null +++ b/tests/testthat/test-ollama.R @@ -0,0 +1,172 @@ +test_that("multiplication works", { + skip_if_no_ollama() + + patientChat <- ellmer::chat_ollama( + model = "gemma4:26b" + ) + + patientChat$chat( + "Hello" + ) |> + expect_no_error() + + jsonSchema <- system.file( + "jsonSchemas", + "cdm54schema-short_deprecated.json", + package = "PatientGenerator" + ) + + checkmate::checkFileExists(jsonSchema) + + prompt <- "5 female patients; + condition occurrence ovarian cancer with concept id 602306, + condition between 2015 and 2020. + All condition occurrences must end one year after index date" + + response_structured <- patientChat$chat_structured( + prompt, + type = ellmer::type_from_schema( + path = jsonSchema + ) + ) + + # Instantiate patientGenerator + expect_no_error({ + patientGenerator <- patientChat$new( + provider = "ollama", + model = "gemma4:26b" + ) + }) + + patientGenerator$save( + name = "ollama-gemma426b-test-set", + ) + + cdm <- TestGenerator::patientsCDM( + testName = "ollama-gemma426b-test-set" + ) + + cdm$person + + patientGenerator$prompt( + "Population (PERSON table): + - 35 persons of various ages born between 1960 and 2000 + - 18 female, use gender_concept_id = 8532 + - 17 male, use gender_concept_id = 8507 + + OBSERVATION_PERIOD: + - Start date between date of birth each person and end of observation 2025-12-31 + - All persons have a period_type_concept_id with id: 32828 + + CONDITION_OCCURRENCE: + - The patients have occurrences of 7 different types of cancer: + - 5 patients from the PERSON table have bladder cancer with condition_concept_id: 196360 + - 5 patients from the PERSON table have breast cancer with condition_concept_id: 36556994 + - 5 patients from the PERSON table have colorectal cancer with condition_concept_id: 40481902 + - 5 patients from the PERSON table have esophageal cancer with condition_concept_id: 4181343 + - 5 patients from the PERSON table have lung cancer with condition_concept_id: 36535703 + - 5 patients from the PERSON table have prostate cancer with condition_concept_id: 4163261 + - 5 patients from the PERSON table have skin melanoma with condition_concept_id: 141232 + - Everyone has condition_type_concept_id 32817 + - For each group of 5 patients sharing the same condition_concept_id: + - 3/5 patients have an occurrence after 2010-01-01 and they were >=18 years + old at condition start date + - If the condition is breast cancer, make all 3 patients Females + - If the condition is prostate cancer, make all 3 patients Males + - 1/5 patients has an occurrence after 2010-01-01 but they were not >=18 + years old at condition start date + - 1/5 patients has an occurrence before 2010-01-01 + + DEATH: + - For every group of patients with same condition_concept_id: + - Among the 3 patients that are >=18 years old and with condition occurrence after 2010-01-01: + - 1/3 patient has a death date previous to the start date of his/her condition occurrence + - 1/3 patient has a death date coinciding with the start date of his/her condition occurrence + + Output Requirements: + - All records in CONDITION_OCCURRENCE, DEATH + - Fill only specified tables in this prompt + - All patients in PERSON have an observation period + - Make sure there's + - Fill out the condition end date 2024-12-31 for everyone + - All condition occurrence records must be inside observation_period dates." + ) + + patientGenerator$save( + name = "testCancerCohortsLLM" + ) + + cdm <- TestGenerator::patientsCDM( + testName = "testCancerCohortsLLM", + cdmVersion = "5.4" + ) + + # call createCancerCohorts to generate codelists and create cohorts + cdm <- createCancerCohorts( + cdm = cdm, + concept_sets_folder = "cancer_cohorts", + name = "cancer_cohorts" + ) + + # test number of patients in cdm instance + cdm$person |> + dplyr::collect() |> + nrow() |> + expect_equal(35) + + # test total attrition + cdm$cancer_cohorts |> + CohortConstructor::attrition() |> + dplyr::select(excluded_records) |> + sum() |> + expect_equal(28) + + # test attrition after imposing age ≥18 + cdm$cancer_cohorts |> + CohortConstructor::attrition() |> + dplyr::filter(stringr::str_detect(reason, "Age requirement")) |> + dplyr::pull(excluded_records) |> + sum() |> + expect_equal(7) + + # test attrition after imposing start date 2010-01-01 + cdm$cancer_cohorts |> + CohortConstructor::attrition() |> + dplyr::filter(stringr::str_detect(reason, "2010-01-01")) |> + dplyr::pull(excluded_records) |> + sum() |> + expect_equal(7) + + # test attrition after excluding people with death date before index date + cdm$cancer_cohorts |> + CohortConstructor::attrition() |> + dplyr::filter(stringr::str_detect(reason, "Not in table death between -Inf & -1 days")) |> + dplyr::pull(excluded_records) |> + sum() |> + expect_equal(7) + + # test attrition after excluding people with death date on index date + cdm$cancer_cohorts |> + CohortConstructor::attrition() |> + dplyr::filter(stringr::str_detect(reason, "Not in table death between 0 & 0 days")) |> + dplyr::pull(excluded_records) |> + sum() |> + expect_equal(7) + + # test number of final patients in the cohort + cdm$cancer_cohorts |> + dplyr::collect() |> + nrow() |> + expect_equal(7) + + # test valid sex variable + cdm$cancer_cohorts |> + PatientProfiles::addSex() |> + dplyr::pull(sex) |> + unique() |> + expect_in(c( + "Male", + "Female") + ) + +}) \ No newline at end of file diff --git a/tests/testthat/testCases/ollama-gemma426b-test-set.json b/tests/testthat/testCases/ollama-gemma426b-test-set.json new file mode 100644 index 0000000..7555b71 --- /dev/null +++ b/tests/testthat/testCases/ollama-gemma426b-test-set.json @@ -0,0 +1,250 @@ +{ + "person": [ + { + "person_id": 1, + "gender_concept_id": 8532, + "year_of_birth": 1961, + "month_of_birth": 2, + "day_of_birth": 14, + "birth_datetime": "1961-02-14T00:00:00Z", + "race_concept_id": 8527, + "ethnicity_concept_id": 38003564, + "location_id": null, + "provider_id": null, + "care_site_id": null, + "person_source_value": "P1", + "gender_source_value": "Female", + "gender_source_concept_id": 8532, + "race_source_value": "White", + "race_source_concept_id": 8527, + "ethnicity_source_value": "Not Hispanic or Latino", + "ethnicity_source_concept_id": 38003564 + }, + { + "person_id": 2, + "gender_concept_id": 8532, + "year_of_birth": 1958, + "month_of_birth": 7, + "day_of_birth": 9, + "birth_datetime": "1958-07-09T00:00:00Z", + "race_concept_id": 8516, + "ethnicity_concept_id": 38003564, + "location_id": null, + "provider_id": null, + "care_site_id": null, + "person_source_value": "P2", + "gender_source_value": "Female", + "gender_source_concept_id": 8532, + "race_source_value": "Black or African American", + "race_source_concept_id": 8516, + "ethnicity_source_value": "Not Hispanic or Latino", + "ethnicity_source_concept_id": 38003564 + }, + { + "person_id": 3, + "gender_concept_id": 8532, + "year_of_birth": 1967, + "month_of_birth": 11, + "day_of_birth": 21, + "birth_datetime": "1967-11-21T00:00:00Z", + "race_concept_id": 8657, + "ethnicity_concept_id": 38003563, + "location_id": null, + "provider_id": null, + "care_site_id": null, + "person_source_value": "P3", + "gender_source_value": "Female", + "gender_source_concept_id": 8532, + "race_source_value": "Asian", + "race_source_concept_id": 8657, + "ethnicity_source_value": "Hispanic or Latino", + "ethnicity_source_concept_id": 38003563 + }, + { + "person_id": 4, + "gender_concept_id": 8532, + "year_of_birth": 1972, + "month_of_birth": 4, + "day_of_birth": 3, + "birth_datetime": "1972-04-03T00:00:00Z", + "race_concept_id": 8527, + "ethnicity_concept_id": 38003564, + "location_id": null, + "provider_id": null, + "care_site_id": null, + "person_source_value": "P4", + "gender_source_value": "Female", + "gender_source_concept_id": 8532, + "race_source_value": "White", + "race_source_concept_id": 8527, + "ethnicity_source_value": "Not Hispanic or Latino", + "ethnicity_source_concept_id": 38003564 + }, + { + "person_id": 5, + "gender_concept_id": 8532, + "year_of_birth": 1955, + "month_of_birth": 9, + "day_of_birth": 30, + "birth_datetime": "1955-09-30T00:00:00Z", + "race_concept_id": 8557, + "ethnicity_concept_id": 38003564, + "location_id": null, + "provider_id": null, + "care_site_id": null, + "person_source_value": "P5", + "gender_source_value": "Female", + "gender_source_concept_id": 8532, + "race_source_value": "Unknown", + "race_source_concept_id": 8557, + "ethnicity_source_value": "Not Hispanic or Latino", + "ethnicity_source_concept_id": 38003564 + } + ], + "observation_period": [ + { + "observation_period_id": 1, + "person_id": 1, + "observation_period_start_date": "2014-01-01", + "observation_period_end_date": "2021-12-31", + "period_type_concept_id": 44814724 + }, + { + "observation_period_id": 2, + "person_id": 2, + "observation_period_start_date": "2014-01-01", + "observation_period_end_date": "2021-12-31", + "period_type_concept_id": 44814724 + }, + { + "observation_period_id": 3, + "person_id": 3, + "observation_period_start_date": "2014-01-01", + "observation_period_end_date": "2021-12-31", + "period_type_concept_id": 44814724 + }, + { + "observation_period_id": 4, + "person_id": 4, + "observation_period_start_date": "2014-01-01", + "observation_period_end_date": "2021-12-31", + "period_type_concept_id": 44814724 + }, + { + "observation_period_id": 5, + "person_id": 5, + "observation_period_start_date": "2014-01-01", + "observation_period_end_date": "2021-12-31", + "period_type_concept_id": 44814724 + } + ], + "death": [], + "visit_occurrence": [], + "visit_detail": [], + "condition_occurrence": [ + { + "condition_occurrence_id": 1, + "person_id": 1, + "condition_concept_id": 602306, + "condition_start_date": "2015-03-17", + "condition_start_datetime": "2015-03-17T00:00:00Z", + "condition_end_date": "2016-03-17", + "condition_end_datetime": "2016-03-17T00:00:00Z", + "condition_type_concept_id": 32817, + "condition_status_concept_id": null, + "stop_reason": null, + "provider_id": null, + "visit_occurrence_id": null, + "visit_detail_id": null, + "condition_source_value": "602306", + "condition_source_concept_id": 602306, + "condition_status_source_value": null + }, + { + "condition_occurrence_id": 2, + "person_id": 2, + "condition_concept_id": 602306, + "condition_start_date": "2016-08-04", + "condition_start_datetime": "2016-08-04T00:00:00Z", + "condition_end_date": "2017-08-04", + "condition_end_datetime": "2017-08-04T00:00:00Z", + "condition_type_concept_id": 32817, + "condition_status_concept_id": null, + "stop_reason": null, + "provider_id": null, + "visit_occurrence_id": null, + "visit_detail_id": null, + "condition_source_value": "602306", + "condition_source_concept_id": 602306, + "condition_status_source_value": null + }, + { + "condition_occurrence_id": 3, + "person_id": 3, + "condition_concept_id": 602306, + "condition_start_date": "2017-11-12", + "condition_start_datetime": "2017-11-12T00:00:00Z", + "condition_end_date": "2018-11-12", + "condition_end_datetime": "2018-11-12T00:00:00Z", + "condition_type_concept_id": 32817, + "condition_status_concept_id": null, + "stop_reason": null, + "provider_id": null, + "visit_occurrence_id": null, + "visit_detail_id": null, + "condition_source_value": "602306", + "condition_source_concept_id": 602306, + "condition_status_source_value": null + }, + { + "condition_occurrence_id": 4, + "person_id": 4, + "condition_concept_id": 602306, + "condition_start_date": "2018-05-26", + "condition_start_datetime": "2018-05-26T00:00:00Z", + "condition_end_date": "2019-05-26", + "condition_end_datetime": "2019-05-26T00:00:00Z", + "condition_type_concept_id": 32817, + "condition_status_concept_id": null, + "stop_reason": null, + "provider_id": null, + "visit_occurrence_id": null, + "visit_detail_id": null, + "condition_source_value": "602306", + "condition_source_concept_id": 602306, + "condition_status_source_value": null + }, + { + "condition_occurrence_id": 5, + "person_id": 5, + "condition_concept_id": 602306, + "condition_start_date": "2020-01-15", + "condition_start_datetime": "2020-01-15T00:00:00Z", + "condition_end_date": "2021-01-15", + "condition_end_datetime": "2021-01-15T00:00:00Z", + "condition_type_concept_id": 32817, + "condition_status_concept_id": null, + "stop_reason": null, + "provider_id": null, + "visit_occurrence_id": null, + "visit_detail_id": null, + "condition_source_value": "602306", + "condition_source_concept_id": 602306, + "condition_status_source_value": null + } + ], + "drug_exposure": [], + "procedure_occurrence": [], + "measurement": [], + "observation": [], + "device_exposure": [], + "specimen": [], + "note": [], + "note_nlp": [], + "location": [], + "care_site": [], + "provider": [], + "payer_plan_period": [], + "cost": [], + "fact_relationship": [] +} diff --git a/tests/testthat/testCases/patient-chat-test.json b/tests/testthat/testCases/patient-chat-test.json index 5d37c05..b60ed4e 100644 --- a/tests/testthat/testCases/patient-chat-test.json +++ b/tests/testthat/testCases/patient-chat-test.json @@ -241,7 +241,7 @@ "ethnicity_source_concept_id": 38003563 }, { - "person_id": 13, + "person_id": 4, "gender_concept_id": 8532, "year_of_birth": 1970, "month_of_birth": 4, @@ -2050,6 +2050,7 @@ "dose_unit_source_value": "day" } ], + "drug_exposure": [], "procedure_occurrence": [], "measurement": [ {