Skip to content

Commit

Permalink
Merge pull request #96 from merefield/function_force_options
Browse files Browse the repository at this point in the history
FEATURE: provide more options to influence function behaviour
  • Loading branch information
merefield authored Jun 7, 2024
2 parents 7f005cd + 3c0f2f2 commit 7033697
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 15 deletions.
2 changes: 1 addition & 1 deletion config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ en:
chatbot_forum_search_function_results_content_type: "The scope of content to be returned in the search results. Choose 'posts' for just ranking Posts, 'topics' for the entire Topics that contain those ranked Posts"
chatbot_forum_search_function_results_topic_max_posts_count_strategy: "The strategy used to determine the maximum number of Posts to be returned in the search results if content_type is 'topics'. Choose 'all' for all Posts, 'just_enough' to limit the Posts to only those up to including the ranked Post, 'stretch_if_required' to include all Posts up to the ranked Post regardless of the max setting, 'exact' for exactly the number of Posts specified in the max setting"
chatbot_forum_search_function_results_topic_max_posts_count: "The maximum number of Posts to be returned in the search results if content_type is 'topics'"
chatbot_forum_search_function_force: "(EXPERIMENTAL): Force the bot to search the forum for information at the start of every response cycle. Will likely significantly increase token usage but potentially help with knowledge."
chatbot_tool_choice_first_iteration: "(EXPERIMENTAL): Choose if a tool must be used for the first iteration of the bots response thinking cycle. If you choose 'force_local_forum_search' the bot will search the forum for information, if you choose 'force_a_function' the bot will use a function of its choice, if you choose 'not_forced' (default) the bot will not be forced to use a tool for its first cycle."
chatbot_forum_search_function_hybrid_search: "(EXPERIMENTAL): Enable hybrid search mode. This will cause the bot to search using native keyword search in addition to embedding based semantic search and it will attempt to blend the results"
chatbot_url_integrity_check: "(EXPERIMENTAL): Enable URL integrity check. If the LLM has returned an invalid forum URL, this will be highlighted back to the LLM"
chatbot_locations_plugin_support: "(EXPERIMENTAL currently user locations only) Natural language querying capability for <a target='_blank' rel='noopener' href='https://github.com/paviliondev/discourse-locations'>Locations Plugin</a> (when using RAG mode, requires Locations Plugin to be installed)"
Expand Down
9 changes: 7 additions & 2 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,14 @@ plugins:
default: 3
min: 1
max: 20
chatbot_forum_search_function_force:
chatbot_tool_choice_first_iteration:
client: false
default: false
type: enum
default: not_forced
choices:
- not_forced
- force_a_function
- force_local_forum_search
chatbot_forum_search_function_hybrid_search:
client: false
default: false
Expand Down
31 changes: 22 additions & 9 deletions lib/discourse_chatbot/bots/open_ai_bot_rag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ module ::DiscourseChatbot

class OpenAiBotRag < OpenAIBotBase

NOT_FORCED = "not_forced"
FORCE_A_FUNCTION = "force_a_function"
FORCE_LOCAL_SEARCH_FUNCTION = "force_local_forum_search"

def initialize(opts)
super
merge_functions(opts)
Expand Down Expand Up @@ -100,7 +104,7 @@ def create_func_mapping(functions)
functions.each_with_object({}) { |func, mapping| mapping[func.name] = func }
end

def create_chat_completion(messages, use_functions = true, force_search = false)
def create_chat_completion(messages, use_functions = true, iteration)
begin
::DiscourseChatbot.progress_debug_message <<~EOS
I called the LLM to help me
Expand All @@ -118,9 +122,16 @@ def create_chat_completion(messages, use_functions = true, force_search = false)
presence_penalty: SiteSetting.chatbot_request_presence_penalty / 100.0
}

parameters.merge!(tools: @tools) if use_functions && @tools

parameters.merge!(tool_choice: {"type": "function", "function": {"name": "local_forum_search"}}) if use_functions && @tools && force_search
if use_functions && @tools
parameters.merge!(tools: @tools)
if iteration == 1
if SiteSetting.chatbot_tool_choice_first_iteration == FORCE_A_FUNCTION
parameters.merge!(tool_choice: "required")
elsif SiteSetting.chatbot_tool_choice_first_iteration == FORCE_LOCAL_SEARCH_FUNCTION
parameters.merge!(tool_choice: {"type": "function", "function": {"name": "local_forum_search"}})
end
end
end

res = @client.chat(
parameters: parameters
Expand Down Expand Up @@ -155,7 +166,7 @@ def generate_response(opts)
# Iteration: #{iteration}
-------------------------------
EOS
res = create_chat_completion(@chat_history + @inner_thoughts, true, iteration == 1 && SiteSetting.chatbot_forum_search_function_force)
res = create_chat_completion(@chat_history + @inner_thoughts, true, iteration)

if res.dig("error")
error_text = "ERROR when trying to perform chat completion: #{res.dig("error", "message")}"
Expand All @@ -164,10 +175,12 @@ def generate_response(opts)
end

finish_reason = res["choices"][0]["finish_reason"]
tools_calls = res["choices"][0]["message"]["tool_calls"]

# the tools calls check is a workaround and is required because sending a query with tools: required leads to an apparently incorrect finish reason.

if (['stop','length'].include?(finish_reason) || @inner_thoughts.length > 7) &&
!(iteration == 1 && SiteSetting.chatbot_forum_search_function_force)
if SiteSetting.chatbot_url_integrity_check
if (['stop','length'].include?(finish_reason) && tools_calls.nil? || @inner_thoughts.length > 7)
if iteration > 1 && SiteSetting.chatbot_url_integrity_check
if legal_urls?(res["choices"][0]["message"]["content"], @posts_ids_found, @topic_ids_found)
return res
else
Expand All @@ -176,7 +189,7 @@ def generate_response(opts)
else
return res
end
elsif finish_reason == 'tool_calls' || (iteration == 1 && SiteSetting.chatbot_forum_search_function_force)
elsif finish_reason == 'tool_calls' || !tools_calls.nil?
handle_function_call(res, opts)
else
raise "Unexpected finish reason: #{finish_reason}"
Expand Down
2 changes: 1 addition & 1 deletion plugin.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# name: discourse-chatbot
# about: a plugin that allows you to have a conversation with a configurable chatbot in Discourse Chat, Topics and Private Messages
# version: 0.9.25
# version: 0.9.26
# authors: merefield
# url: https://github.com/merefield/discourse-chatbot

Expand Down
4 changes: 2 additions & 2 deletions spec/lib/bot/open_ai_agent_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
first_query = get_chatbot_input_fixture("llm_first_query").unshift(system_entry)
second_query = get_chatbot_input_fixture("llm_second_query").unshift(system_entry)

described_class.any_instance.expects(:create_chat_completion).with(first_query, true, false).returns(llm_function_response)
described_class.any_instance.expects(:create_chat_completion).with(second_query, true, false).returns(llm_final_response)
described_class.any_instance.expects(:create_chat_completion).with(first_query, true, 1).returns(llm_function_response)
described_class.any_instance.expects(:create_chat_completion).with(second_query, true, 2).returns(llm_final_response)

expect(rag.get_response(query, opts)[:reply]).to eq(llm_final_response["choices"][0]["message"]["content"])
end
Expand Down

0 comments on commit 7033697

Please sign in to comment.