Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions guides/google.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ Passed via `:provider_options` keyword:
- **Example**: `provider_options: [google_grounding: %{enable: true}]`
- **Cost tracking**: Usage tracked in `response.usage.tool_usage.web_search` with `unit: "query"`

### `google_url_context`
- **Type**: `boolean` | `map`
- **Purpose**: Enable URL context grounding - allows model to fetch and use content from specific URLs
- **Example**: `provider_options: [google_url_context: true]`
- **Note**: Requires v1beta (default)

### `google_thinking_budget`
- **Type**: Non-negative integer
- **Purpose**: Control thinking tokens for Gemini 2.5 models
Expand Down
23 changes: 17 additions & 6 deletions lib/req_llm/providers/google.ex
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ defmodule ReqLLM.Providers.Google do
doc:
"Enable Google Search grounding - allows model to search the web. Set to %{enable: true} for modern models, or %{dynamic_retrieval: %{mode: \"MODE_DYNAMIC\", dynamic_threshold: 0.7}} for Gemini 1.5 legacy support. Requires v1beta (default)."
],
google_url_context: [
type: {:or, [:boolean, :map]},
doc:
"Enable URL context grounding - allows model to fetch and use content from specific URLs. Pass `true` or a map with options. Requires v1beta (default)."
],
dimensions: [
type: :pos_integer,
doc:
Expand Down Expand Up @@ -896,28 +901,30 @@ defmodule ReqLLM.Providers.Google do

tool_config = build_google_tool_config(request.options[:tool_choice])

grounding_tools = build_grounding_tools(request.options[:google_grounding])
url_context_tools = build_url_context_tools(request.options[:google_url_context])
builtin_tools = grounding_tools ++ url_context_tools

tools_data =
case request.options[:tools] do
tools when is_list(tools) and tools != [] ->
grounding_tools = build_grounding_tools(request.options[:google_grounding])

user_tools = [
%{functionDeclarations: Enum.map(tools, &ReqLLM.Tool.to_schema(&1, :google))}
]

all_tools = grounding_tools ++ user_tools
all_tools = builtin_tools ++ user_tools

%{tools: all_tools}
|> maybe_put(:toolConfig, tool_config)

_ ->
case build_grounding_tools(request.options[:google_grounding]) do
case builtin_tools do
[] ->
%{}
|> maybe_put(:toolConfig, tool_config)

grounding_tools ->
%{tools: grounding_tools}
tools ->
%{tools: tools}
|> maybe_put(:toolConfig, tool_config)
end
end
Expand Down Expand Up @@ -1341,6 +1348,10 @@ defmodule ReqLLM.Providers.Google do

defp build_grounding_tools(_), do: []

defp build_url_context_tools(true), do: [%{url_context: %{}}]
defp build_url_context_tools(%{} = opts), do: [%{url_context: opts}]
defp build_url_context_tools(_), do: []

defp extract_grounding_metadata(%{"candidates" => [candidate | _]}) do
case candidate do
%{"groundingMetadata" => metadata} when is_map(metadata) ->
Expand Down
109 changes: 109 additions & 0 deletions test/providers/google_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,115 @@ defmodule ReqLLM.Providers.GoogleTest do
end
end

describe "google_url_context option" do
test "encode_body includes url_context tool when boolean true" do
{:ok, model} = ReqLLM.model("google:gemini-2.0-flash")
context = context_fixture()

mock_request = %Req.Request{
options: [
context: context,
model: model.model,
stream: false,
google_url_context: true
]
}

updated_request = Google.encode_body(mock_request)
decoded = Jason.decode!(updated_request.body)

assert is_list(decoded["tools"])
assert length(decoded["tools"]) == 1
[tool] = decoded["tools"]
assert Map.has_key?(tool, "url_context")
assert tool["url_context"] == %{}
end

test "encode_body includes url_context tool with map options" do
{:ok, model} = ReqLLM.model("google:gemini-2.0-flash")
context = context_fixture()

mock_request = %Req.Request{
options: [
context: context,
model: model.model,
stream: false,
google_url_context: %{some_option: "value"}
]
}

updated_request = Google.encode_body(mock_request)
decoded = Jason.decode!(updated_request.body)

assert is_list(decoded["tools"])
assert length(decoded["tools"]) == 1
[tool] = decoded["tools"]
assert Map.has_key?(tool, "url_context")
assert tool["url_context"] == %{"some_option" => "value"}
end

test "encode_body combines url_context with user tools" do
{:ok, model} = ReqLLM.model("google:gemini-2.0-flash")
context = context_fixture()

tool =
ReqLLM.Tool.new!(
name: "test_tool",
description: "A test tool",
parameter_schema: [
name: [type: :string, required: true, doc: "A name parameter"]
],
callback: fn _ -> {:ok, "result"} end
)

mock_request = %Req.Request{
options: [
context: context,
model: model.model,
stream: false,
tools: [tool],
google_url_context: true,
operation: :chat
]
}

updated_request = Google.encode_body(mock_request)
decoded = Jason.decode!(updated_request.body)

assert is_list(decoded["tools"])
assert length(decoded["tools"]) == 2

tool_types = Enum.map(decoded["tools"], fn t -> Map.keys(t) end) |> List.flatten()
assert "url_context" in tool_types
assert "functionDeclarations" in tool_types
end

test "encode_body combines url_context with grounding" do
{:ok, model} = ReqLLM.model("google:gemini-2.0-flash")
context = context_fixture()

mock_request = %Req.Request{
options: [
context: context,
model: model.model,
stream: false,
google_grounding: %{enable: true},
google_url_context: true
]
}

updated_request = Google.encode_body(mock_request)
decoded = Jason.decode!(updated_request.body)

assert is_list(decoded["tools"])
assert length(decoded["tools"]) == 2

tool_types = Enum.map(decoded["tools"], fn t -> Map.keys(t) end) |> List.flatten()
assert "url_context" in tool_types
assert "google_search" in tool_types
end
end

describe "response decoding" do
test "decode_response handles non-streaming responses" do
# Create a mock Google response
Expand Down