diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 245cdfd..e52605e 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -79,11 +79,16 @@ en: chatbot_escalate_to_staff_max_history: "(Chat only) number of chat messages included in transcript added to escalation PM" chatbot_user_fields_collection: "(EXPERIMENTAL) Collect empty user fields from the user as part of the conversation" chatbot_news_api_token: "News API token for news (if left blank, news will never be searched)Get one at NewsAPI.org" + chatbot_news_api_call_token_cost: "The notional token cost used for each call to the News API against users' overall quota." chatbot_firecrawl_api_token: "Firecrawl API token for crawling remote websites. If left blank, crawling will not be available. Get one at https://www.firecrawl.dev/" + chatbot_firecrawl_api_call_token_cost: "The notional token cost used for each call to the Firecrawl API against users' overall quota." chatbot_jina_api_token: "Jina API token for web crawl and search. Get one at https://jina.ai. Alternative to Firecrawl and Serp. If Firecrawl API token is populated, it will be used in preference to Jina for crawling. Ditto Serp API for Searching." + chatbot_jina_api_token_cost_multiplier: "The notional token cost multiplier used for each call to the Jina API against users' overall quota. A Users's cost is the amount of text returned multiplied by this number." chatbot_function_response_char_limit: "The maximum number of characters taken from the response from web crawl in order to mitigate the risk of a token breach when later including result in LLM call" chatbot_serp_api_key: "Serp API token for google search (if left blank, google will never be searched). Get one at SerpAPI.com" + chatbot_serp_api_call_token_cost: "The notional token cost used for each call to the Serp API against users' overall quota." chatbot_marketstack_key: "Marketstack API key for stock price information (if left blank, Marketstack will never be queried).Get one at MarketStack.com" + chatbot_marketstack_api_call_token_cost: "The notional token cost used for each call to the Marketstack API against users' overall quota." chatbot_enable_verbose_console_logging: "Enable response retrieval progress logging to console to help debug issues" chatbot_enable_verbose_rails_logging: "Enable response retrieval progress logging to rails logs to help debug issues. 'api_calls_only' restricts this to just API calls, 'all' logs all progress" chatbot_verbose_rails_logging_destination_level: "Choose which category of logs to send verbose logs to. 'warn' is useful in Production as 'info' logs are not exposed at /logs." diff --git a/config/settings.yml b/config/settings.yml index 1a5a2f9..d5b2e01 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -373,21 +373,36 @@ plugins: chatbot_news_api_token: client: false default: '' + chatbot_news_api_call_token_cost: + client: false + default: 10000 chatbot_firecrawl_api_token: client: false default: '' + chatbot_firecrawl_api_call_token_cost: + client: false + default: 10000 chatbot_jina_api_token: client: false default: '' + chatbot_jina_api_token_cost_multiplier: + client: false + default: 10000 chatbot_function_response_char_limit: client: false default: 350000 chatbot_serp_api_key: client: false default: '' + chatbot_serp_api_call_token_cost: + client: false + default: 10000 chatbot_marketstack_key: client: false default: '' + chatbot_marketstack_api_call_token_cost: + client: false + default: 10000 chatbot_enable_verbose_console_logging: client: false default: false diff --git a/lib/discourse_chatbot/functions/news_function.rb b/lib/discourse_chatbot/functions/news_function.rb index 05bd48a..0c54519 100644 --- a/lib/discourse_chatbot/functions/news_function.rb +++ b/lib/discourse_chatbot/functions/news_function.rb @@ -4,7 +4,6 @@ module DiscourseChatbot class NewsFunction < Function - TOKEN_COST = 10000 def name 'news' @@ -33,6 +32,7 @@ def process(args) -------------------------------------- EOS super(args) + token_usage = 0 conn_params = {} @@ -56,14 +56,15 @@ def process(args) all_articles.each do |a| news += "#{a["title"]}. " end + token_usage = SiteSetting.chatbot_news_api_call_token_cost { answer: news, - token_usage: TOKEN_COST + token_usage: token_usage } rescue { answer: I18n.t("chatbot.prompt.function.news.error"), - token_usage: TOKEN_COST + token_usage: token_usage } end end diff --git a/lib/discourse_chatbot/functions/stock_data_function.rb b/lib/discourse_chatbot/functions/stock_data_function.rb index c168f71..4304563 100644 --- a/lib/discourse_chatbot/functions/stock_data_function.rb +++ b/lib/discourse_chatbot/functions/stock_data_function.rb @@ -9,7 +9,6 @@ module DiscourseChatbot class StockDataFunction < Function - TOKEN_COST = 1000 def name 'stock_data' @@ -33,6 +32,7 @@ def required def process(args) begin super(args) + token_usage = 0 params = { access_key: "#{SiteSetting.chatbot_marketstack_key}", @@ -57,15 +57,16 @@ def process(args) api_response = JSON.parse(json) stock_data = api_response['data'][0] + token_usage = SiteSetting.chatbot_marketstack_api_call_token_cost { answer: I18n.t("chatbot.prompt.function.stock_data.answer", ticker: stock_data['symbol'], close: stock_data['close'].to_s, date: stock_data['date'].to_s, high: stock_data['high'].to_s, low: stock_data['low'].to_s), - token_usage: TOKEN_COST + token_usage: token_usage } rescue { answer: I18n.t("chatbot.prompt.function.stock_data.error"), - token_usage: TOKEN_COST + token_usage: token_usage } end end diff --git a/lib/discourse_chatbot/functions/web_crawler_function.rb b/lib/discourse_chatbot/functions/web_crawler_function.rb index fe3fe7f..b0f3e39 100644 --- a/lib/discourse_chatbot/functions/web_crawler_function.rb +++ b/lib/discourse_chatbot/functions/web_crawler_function.rb @@ -31,6 +31,7 @@ def process(args) -------------------------------------- EOS super(args) + token_usage = 0 if SiteSetting.chatbot_firecrawl_api_token.blank? conn = Faraday.new( url: "https://r.jina.ai/#{args[parameters[0][:name]]}", @@ -40,6 +41,7 @@ def process(args) ) response = conn.get result = response.body + token_usage = result.length * SiteSetting.chatbot_jina_api_token_cost_multiplier else conn = Faraday.new( url: 'https://api.firecrawl.dev', @@ -71,12 +73,14 @@ def process(args) end result = response_body["data"][0]["markdown"] + token_usage = SiteSetting.chatbot_firecrawl_api_token_cost end { answer: result[0..SiteSetting.chatbot_function_response_char_limit], token_usage: token_usage } - rescue + rescue=> e + Rails.logger.error("Chatbot: Error in web crawler function: #{e}") { answer: I18n.t("chatbot.prompt.function.web_crawler.error"), token_usage: token_usage diff --git a/lib/discourse_chatbot/functions/web_search_function.rb b/lib/discourse_chatbot/functions/web_search_function.rb index 204cbca..e5357e9 100644 --- a/lib/discourse_chatbot/functions/web_search_function.rb +++ b/lib/discourse_chatbot/functions/web_search_function.rb @@ -27,6 +27,7 @@ def required def process(args) begin super(args) + token_usage = 0 if SiteSetting.chatbot_serp_api_key.blank? query = URI.encode_www_form_component(args[parameters[0][:name]]) conn = Faraday.new( @@ -37,6 +38,7 @@ def process(args) ) response = conn.get result = response.body + token_usage = response.body.length * SiteSetting.chatbot_jina_api_token_cost_multiplier else hash_results = ::GoogleSearch.new(q: args[parameters[0][:name]], serp_api_key: SiteSetting.chatbot_serp_api_key) .get_hash @@ -44,6 +46,7 @@ def process(args) result = hash_results.dig(:answer_box, :answer).presence || hash_results.dig(:answer_box, :snippet).presence || hash_results.dig(:organic_results) + token_usage = SiteSetting.chatbot_serp_api_token_cost end { answer: result[0..SiteSetting.chatbot_function_response_char_limit],