Skip to content

Commit

Permalink
Add google_search tool (#567)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattlindsey authored Dec 7, 2024
1 parent d0e8738 commit b393c41
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 14 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,5 @@ group :test do
gem "selenium-webdriver"
gem "minitest-stub_any_instance"
gem "rails-controller-testing"
gem "webmock"
end
9 changes: 9 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ GEM
coderay (1.1.3)
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
crack (1.0.0)
bigdecimal
rexml
crass (1.0.6)
date (3.3.4)
debug (1.8.0)
Expand Down Expand Up @@ -167,6 +170,7 @@ GEM
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
hashdiff (1.1.2)
hashie (5.0.0)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
Expand Down Expand Up @@ -463,6 +467,10 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.24.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.2)
websocket (1.2.11)
websocket-driver (0.7.6)
Expand Down Expand Up @@ -526,6 +534,7 @@ DEPENDENCIES
turbo-rails (~> 2.0.5)
tzinfo-data
web-console
webmock

RUBY VERSION
ruby 3.3.6p108
Expand Down
23 changes: 15 additions & 8 deletions app/helpers/messages_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,23 @@ def format_for_speaking(text)
end

def format_for_display(message, append_inside_tag: nil)
if memory_updated?(message)
return link_to JSON.parse(message.content_text)["message_to_user"],
if message_to_user_from_tool_call?(message)
function_name = message.content_tool_calls.dig(:function, :name)
message_to_user = JSON.parse(message.content_text)["message_to_user"]

case function_name
when "memory_remember_detail_about_user"
return link_to message_to_user,
settings_memories_path,
{ data: { turbo_frame: "_top" }, class: "text-gray-400 dark:!text-gray-500 font-normal no-underline" }
elsif message_to_user_from_tool_call?(message)
return content_tag(:span, JSON.parse(message.content_text)["message_to_user"], class: "text-gray-400 dark:!text-gray-500")
when "googlesearch_google_search"
query = message_to_user.partition(":").last
return link_to message_to_user,
"https://www.google.com/search?q=#{URI.encode_www_form_component(query)}",
{ target: :_blank, data: { turbo_frame: "_top" }, class: "text-gray-400 dark:!text-gray-500 font-normal no-underline" }
else
return content_tag(:span, message_to_user, class: "text-gray-400 dark:!text-gray-500")
end
else
escaped_text = html_escape(message.content_text)

Expand Down Expand Up @@ -74,10 +85,6 @@ def message_to_user_from_tool_call?(message)
false
end

def memory_updated?(message)
message.tool? && message.content_tool_calls.dig(:function, :name) == "memory_remember_detail_about_user"
end

private

def block_code
Expand Down
2 changes: 1 addition & 1 deletion app/services/sdk/verb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def handle(response)
raise ResponseError.new(response) if !response.status.in? @expected_statuses

if response.status.between?(200, 299)
response.body.presence && OpenData.for(JSON.parse(response.body))
response.body.presence && OpenData.for(JSON.parse(response.body)) rescue response
else
response
end
Expand Down
1 change: 1 addition & 0 deletions app/services/toolbox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def self.descendants
test_env && Toolbox::HelloWorld,
Toolbox::OpenMeteo,
Toolbox::Memory,
Toolbox::GoogleSearch,
gmail_active && Toolbox::Gmail,
tasks_active && Toolbox::GoogleTasks,
].compact
Expand Down
32 changes: 32 additions & 0 deletions app/services/toolbox/google_search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Toolbox::GoogleSearch < Toolbox

describe :google_search, <<~S
Search Google for the indicated query.
Use this to answer questions about current events, look up information, or find answers to questions.
Try to use this sparingly; prefer to use the user's memories and the tools you have available to answer questions.
When you do use this, try to use exact queries for which you expect to get a definitive answer.
When you respond to the user, try to include an answer to the question rather than just a link.
S
def google_search(query_s:)
encoded_query = URI.encode_www_form_component(query_s)
response_body = get("https://www.google.com/search").param(q: encoded_query).body
doc = Nokogiri::HTML(response_body)

results = doc.css("div.BNeawe").map do |div|
div.children.map do |node|
if node.name == "a"
anchor_text = node.text.strip
href = node["href"]
"#{anchor_text} (#{href})"
else
node.text.strip
end
end.join(" ")
end.join("\n")

{
message_to_user: "Web query: #{query_s}",
query_results: results
}
end
end
5 changes: 0 additions & 5 deletions test/helpers/messages_helper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

class MessagesHelperTest < ActionView::TestCase

test "memory_updated responds properly for tool result" do
message = messages(:keep_memory_tool_result)
assert memory_updated?(message)
end

test "message_to_user_from_tool_call responds properly for tool result" do
message = messages(:keep_memory_tool_result)
assert message_to_user_from_tool_call?(message)
Expand Down
42 changes: 42 additions & 0 deletions test/services/toolbox/google_search_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require "test_helper"

class Toolbox::GoogleSearchTest < ActiveSupport::TestCase
setup do
@google_search = Toolbox::GoogleSearch.new
WebMock.enable!
end

test "google_search returns the expected result" do
expected_result = {
message_to_user: "Web query: Sandi Metz POODR title",
query_results: "Practical Object-Oriented Design in Ruby by Sandi Metz. Learn more (https://www.poodr.com) for more details."
}
html_content = <<-HTML
<html>
<body>
<div class="BNeawe">
Practical Object-Oriented Design in Ruby by Sandi Metz.#{' '}
<a href="https://www.poodr.com\">Learn more</a> for more details.
</div>
<div class="OtherClass">
Not relevant content.
<a href="https://www.notrelevant.com">Ignore this</a>.
</div>
</body>
</html>
HTML

stub_request(:get, /www.google.com/)
.with(
headers: {
"Accept"=>"*/*",
"Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
"User-Agent"=>"Ruby"
}
)
.to_return(status: 200, body: html_content, headers: {})

result = @google_search.google_search(query_s: "Sandi Metz POODR title")
assert_equal expected_result, result
end
end
4 changes: 4 additions & 0 deletions test/services/toolbox/open_meteo_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class Toolbox::OpenMeteoTest < ActiveSupport::TestCase
end

test "get_current_and_todays_weather hits the API and doesn't fail" do
WebMock.disable!

allow_request(:get, :get_location) do
allow_request(:get, :get_current_and_todays_weather) do
result = @open_meteo.get_current_and_todays_weather(city_s: "Austin", state_province_or_region_s: "Texas")
Expand All @@ -35,6 +37,8 @@ class Toolbox::OpenMeteoTest < ActiveSupport::TestCase
end

test "get_current_and_todays_weather works as a tool call" do
WebMock.disable!

allow_request(:get, :get_location) do
allow_request(:get, :get_current_and_todays_weather) do
result = Toolbox.call("openmeteo_get_current_and_todays_weather", city: "Austin", state_province_or_region: "Texas")
Expand Down
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require "rails/test_help"
require "minitest/autorun"
require "pry"
require "webmock/minitest"

Dir[Rails.root.join("test/support/**/*.rb")].sort.each { |file| require file }

Expand Down

0 comments on commit b393c41

Please sign in to comment.