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": [
{