Skip to content

Commit

Permalink
Merge pull request #22 from bakaburg1/Dev
Browse files Browse the repository at this point in the history
Improved LLM Handling and Transcript Management
  • Loading branch information
bakaburg1 authored May 29, 2024
2 parents 8fe8e60 + cb720ed commit 1383da7
Show file tree
Hide file tree
Showing 19 changed files with 235 additions and 99 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: minutemaker
Title: GenAI-based meeting and conferences minutes generator
Version: 0.9.0
Version: 0.10.0
Authors@R:
person("Angelo", "D'Ambrosio", , "a.dambrosioMD@gmail.com", role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-2045-5155"))
Expand Down
23 changes: 2 additions & 21 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,21 +1,2 @@
MIT License

Copyright (c) 2023 Angelo D'Ambrosio

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
YEAR: 2023
COPYRIGHT HOLDER: Angelo D'Ambrosio
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MIT License

Copyright (c) 2023 minutemaker authors
Copyright (c) 2023 Angelo D'Ambrosio

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export(generate_recording_details)
export(get_prompts)
export(import_transcript_from_file)
export(infer_agenda_from_transcript)
export(interrogate_llm)
export(merge_transcripts)
export(parse_transcript_json)
export(perform_speech_to_text)
export(prompt_llm)
export(run_in_terminal)
export(set_prompts)
export(speech_to_summary_workflow)
Expand Down
19 changes: 19 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# minutemaker 0.10.0

#### Enhancements
- **Change interrogate_llm to prompt_llm**: Renamed the `interrogate_llm` function to `prompt_llm` to better reflect its purpose of generating prompts for language models (Commit: [2f9eeddd]).
- **Model Logging for LLM Requests**: Added a `log_request` parameter to `use_openai_llm`, `use_azure_llm`, and `use_custom_llm` functions to log the specific endpoint and model being used (Commit: [7e85f2f]).
- **Handling Long LLM Responses**: Improved the handling of LLM responses that exceed the output token limit. Users are now prompted to decide how to proceed, and incomplete answers are saved to a file for reference (Commit: [18cfada]).
- **Model Parameter for Custom LLM APIs**: Added a `model` parameter to LLM calls to allow specifying a model for custom APIs with multiple models (Commit: [cd4227b]).

#### Fixes
- **Restore Correct Speaker Extraction for Webex VTT**: Fixed the parsing of Webex VTT files which was broken by the implementation of MS Teams VTT parsing (Commit: [d189980]).
- **Remove Newlines from JSON Output**: Fixed an issue where some custom LLMs produced invalid JSON with newlines, causing errors during parsing (Commit: [e9e578a]).
- **Support JSON Mode for Custom LLMs**: Ensured that most custom LLMs now support JSON mode by keeping the JSON mode option in the call (Commit: [f4df24c]).

#### Documentation
- **Improve Code Comments and Error Handling**: Enhanced code comments and error handling for better clarity and maintenance (Commit: [4b689ff]).

#### Summary
This pull request introduces several enhancements to the LLM handling, including logging the model being used, better management of long responses, and support for specifying models in custom APIs. It also includes fixes for speaker extraction in Webex VTT files and handling of JSON outputs from custom LLMs. Additionally, code comments and error handling have been improved for better clarity.

# minutemaker 0.9.0

### Improve agenda review and add custom LLM support
Expand Down
111 changes: 94 additions & 17 deletions R/LLM_calls.R
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ process_messages <- function(messages) {

}

#' Interrogate Language Model
#' Interrogate a Language Model
#'
#' This function sends requests to a specified language model provider (OpenAI,
#' Azure, or a locally running LLM server) and returns the response. It handles
Expand Down Expand Up @@ -149,12 +149,12 @@ process_messages <- function(messages) {
#'
#' @examples
#' \dontrun{
#' response <- interrogate_llm(
#' response <- prompt_llm(
#' messages = c(user = "Hello there!"),
#' provider = "openai")
#' }
#'
interrogate_llm <- function(
prompt_llm <- function(
messages = NULL,
provider = getOption("minutemaker_llm_provider"),
params = list(
Expand Down Expand Up @@ -183,7 +183,7 @@ interrogate_llm <- function(

body$messages <- messages

# Force the LLM to answer in JSON format (only openai and azure)
# Force the LLM to answer in JSON format (not all models support this)
if (force_json) {
body$response_format <- list("type" = "json_object")
}
Expand All @@ -192,7 +192,8 @@ interrogate_llm <- function(
llm_fun <- paste0("use_", provider, "_llm")

if (!exists(llm_fun, mode = "function")) {
stop("Unsupported LLM provider.")
stop("Unsupported LLM provider.
You can set it project-wide using the minutemaker_llm_provider option.")
}

llm_fun <- get(llm_fun)
Expand Down Expand Up @@ -252,13 +253,62 @@ interrogate_llm <- function(
) |> message()
}

answer <- parsed$choices[[1]]

if (answer$finish_reason == "length") {
stop("Answer exhausted the context window!")
}

answer$message$content
# Return the response
purrr::imap_chr(parsed$choices, \(ans, i) {
ans_content <- ans$message$content

# Manage the case when the answer is cut off due to exceeding the
# output token limit
if (ans$finish_reason == "length") {
i <- if (length(parsed$choices) > 1) paste0(" ", i, " ") else " "

warning("Answer", i, "exhausted the context window!")

file_name <- paste0("output_", Sys.time(), ".txt")

warning(
"Answer", i, "exhausted the context window!\n",
"The answer has been saved to a file: ", file_name
)

readr::write_lines(ans_content, file_name)

choice <- utils::menu(
c(
"Try to complete the answer",
"Keep the incomplete answer",
"Stop the process"),
title = "How do you want to proceed?"
)

if (choice == 1) {
# Ask the model to continue the answer
messages_new <- c(
messages,
list(list(
role = "assistant",
content = ans_content
)),
list(list(
role = "user",
content = "continue"
))
)

ans_new <- prompt_llm(
messages_new, provider = provider, params = params,
force_json = force_json,
log_request = log_request, ...
)

return(paste0(ans_content, ans_new))
} else if (choice == 2) {
return(ans_content)
} else {
stop("The process has been stopped.")
}
} else ans_content
})
}

#' Use OpenAI Language Model
Expand All @@ -269,13 +319,16 @@ interrogate_llm <- function(
#' @param body The body of the request.
#' @param model Model identifier for the OpenAI API. Obtained from R options.
#' @param api_key API key for the OpenAI service. Obtained from R options.
#' @param log_request A boolean to log the request time. Can be set up globally
#' using the `minutemaker_log_requests` option, which defaults to TRUE.
#'
#' @return The function returns the response from the OpenAI API.
#'
use_openai_llm <- function(
body,
model = getOption("minutemaker_openai_model_gpt"),
api_key = getOption("minutemaker_openai_api_key")
api_key = getOption("minutemaker_openai_api_key"),
log_request = getOption("minutemaker_log_requests", TRUE)
) {

if (is.null(api_key) || is.null(model)) {
Expand All @@ -285,6 +338,10 @@ use_openai_llm <- function(
"minutemaker_open_api_key options.")
}

if (log_request) {
message("Interrogating OpenAI: ", model, "...")
}

body$model = model

# Prepare the request
Expand Down Expand Up @@ -315,14 +372,17 @@ use_openai_llm <- function(
#' options.
#' @param api_version API version for the Azure language model service. Obtained
#' from R options.
#' @param log_request A boolean to log the request time. Can be set up globally
#' using the `minutemaker_log_requests` option, which defaults to TRUE.
#'
#' @return The function returns the response from the Azure API.
use_azure_llm <- function(
body,
deployment_id = getOption("minutemaker_azure_deployment_gpt"),
resource_name = getOption("minutemaker_azure_resource_gpt"),
api_key = getOption("minutemaker_azure_api_key_gpt"),
api_version = getOption("minutemaker_azure_api_version")
api_version = getOption("minutemaker_azure_api_version"),
log_request = getOption("minutemaker_log_requests", TRUE)
) {

if (is.null(resource_name) || is.null(deployment_id) ||
Expand All @@ -337,6 +397,12 @@ use_azure_llm <- function(
)
}

if (log_request) {
message(
"Interrogating Azure OpenAI: ", resource_name, "/", deployment_id,
" (", api_version, ")...")
}

# Prepare the request
httr::POST(
url = paste0(
Expand All @@ -361,15 +427,21 @@ use_azure_llm <- function(
#' @param body The body of the request.
#' @param endpoint The local endpoint for the language model service. Can be
#' obtained from R options.
#' @param model Model identifier for the custom API, if needed (some API have
#' one model per endpoint, some multiple ones). Obtained from R options.
#' @param api_key Optional API key for the custom language model services that
#' require it. Obtained from R options.
#' @param log_request A boolean to log the request time. Can be set up globally
#' using the `minutemaker_log_requests` option, which defaults to TRUE.
#'
#' @return The function returns the response from the local language model
#' endpoint.
use_custom_llm <- function(
body,
endpoint = getOption("minutemaker_custom_endpoint_gpt"),
api_key = getOption("minutemaker_custom_api_key")
model = getOption("minutemaker_custom_model_gpt", NULL),
api_key = getOption("minutemaker_custom_api_key"),
log_request = getOption("minutemaker_log_requests", TRUE)
) {

if (is.null(endpoint)) {
Expand All @@ -379,7 +451,13 @@ use_custom_llm <- function(
)
}

body$response_format <- NULL
if (log_request) {
message("Interrogating custom LLM: ", endpoint, "/", model, "...")
}

if (!is.null(model)) {
body$model = model
}

# Prepare the request
httr::POST(
Expand All @@ -393,4 +471,3 @@ use_custom_llm <- function(
)

}

27 changes: 15 additions & 12 deletions R/data_management.R
Original file line number Diff line number Diff line change
Expand Up @@ -557,8 +557,9 @@ format_summary_tree <- function(
agenda_element$title <- ifelse(is.null(
agenda_element$title), "", agenda_element$title)

output_piece <- stringr::str_glue_data(agenda_element,
"Session: {session};
output_piece <- stringr::str_glue_data(
agenda_element,
"Session: {session};
Title: {title};
Speakers: {speakers};
Moderators: {moderators};
Expand Down Expand Up @@ -597,8 +598,8 @@ format_summary_tree <- function(
#'
#' @export
format_agenda <- function(
agenda,
event_start_time = getOption("minutemaker_event_start_time")
agenda,
event_start_time = getOption("minutemaker_event_start_time")
) {

# Import agenda from file
Expand All @@ -624,8 +625,9 @@ format_agenda <- function(
.x$title <- ifelse(is.null(.x$title), "", .x$title)
.x$description <- ifelse(is.null(.x$description), "", .x$description)

stringr::str_glue_data(.x,
"Session: {session};
stringr::str_glue_data(
.x,
"Session: {session};
Title: {title};
Description: {description};
Speakers: {speakers};
Expand Down Expand Up @@ -697,13 +699,14 @@ import_transcript_from_file <- function(
# Normal VTT style
if (stringr::str_detect(cur_speaker, "^\\d+ ")) {
# the name is between double quotes
stringr::str_extract_all('(?<=").*(?=")') |>
unlist()
cur_speaker <- stringr::str_extract_all(
cur_speaker, '(?<=").*(?=")') |>
unlist()
} else if (stringr::str_detect(lines[.x + 1], "^<v ")) {
# MS Teams vtt style: <v speaker first name [second name]>
cur_speaker <- stringr::str_extract_all(
lines[.x + 1], '(?<=<v ).*?(?=>)') |>
unlist()
unlist()
} else {
cur_speaker <- NA
}
Expand Down Expand Up @@ -1097,7 +1100,7 @@ add_chat_transcript <- function(
#' @param llm_provider A string indicating the LLM provider to use for the
#' summarization. See `summarise_transcript` for more details.
#' @param extra_summarise_args Additional arguments passed to the
#' `interrogate_llm` function. See `summarise_transcript` for more details.
#' `prompt_llm` function. See `summarise_transcript` for more details.
#' @param summarization_window_size The size of the summarization window in
#' minutes if the "rolling" method is used. See `summarise_transcript` for
#' more details.
Expand Down Expand Up @@ -1381,8 +1384,8 @@ speech_to_summary_workflow <- function(
expected_agenda = expected_agenda,
window_size = agenda_generation_window_size,
output_file = if (!purrr::is_empty(agenda) && is.character(agenda)) {
file.path(target_dir, basename(agenda))
} else file.path(target_dir, "agenda.R"),
file.path(target_dir, basename(agenda))
} else file.path(target_dir, "agenda.R"),
provider = llm_provider
), extra_agenda_generation_args)

Expand Down
Loading

0 comments on commit 1383da7

Please # to comment.