Skip to content

Commit 526eb11

Browse files
committed
Release v0.8.8: Extract shared utilities and remove dead code
Consolidate duplicated helper functions into dedicated utility modules and remove unused code paths discovered during codebase audit. New utility modules: lib/gemini/utils/map_helpers.ex - maybe_put/3: conditionally add to map if value not nil - maybe_put_non_empty/3: skip nil and empty strings - maybe_put_non_zero/3: skip nil and zero values - build_paginated_path/2: construct paths with pagination params - add_query_param/3: conditionally add query parameters lib/gemini/utils/polling_helpers.ex - timed_out?/2: check if polling operation exceeded timeout - maybe_add/3: conditionally add to keyword list Modules updated to use shared utilities: - Gemini.APIs.Batches - Gemini.APIs.Documents - Gemini.APIs.FileSearchStores - Gemini.APIs.Files - Gemini.APIs.Interactions - Gemini.APIs.Operations - Gemini.APIs.RagStores - Gemini.APIs.Tunings - Gemini.APIs.Videos - Gemini.Auth.MultiAuthCoordinator - Gemini.Config - Gemini.RateLimiter.RetryManager - Gemini.Types.Generation.Video - Gemini.Types.Interactions.* (content, config, delta, events, etc.) - Gemini.Types.Request.* (embed content types) - Gemini.Types.Schema - Gemini.Types.Tuning Removed dead code: Gemini.Auth - authenticate/2 and base_url/2 delegators (unused) Gemini.Auth.GeminiStrategy - authenticate/1 (headers/1 is the actual interface) Gemini.Auth.MultiAuthCoordinator - Unused struct definition Gemini.Client - stream_post/2 and stream_post_with_auth/4 - Streaming moved to HTTPStreaming module Gemini.Client.HTTP - All SSE streaming functions (parse_sse_stream, handle_stream_*) - Module now focused on regular HTTP requests only Gemini.Config - models/0, use_case_models/0, use_case_token_minima/0 - model_for_use_case/2, resolved_use_case_models/1 Gemini.RateLimiter.ConcurrencyGate - reset/1 (reset_all/0 sufficient for testing) Gemini.RateLimiter.Config - adaptive_enabled?/1, profile_config/1 Gemini.RateLimiter.State - would_exceed_budget?/3 Gemini.Streaming.ToolOrchestrator - subscribe/2, stop/1, cleanup_streams/1 Deleted files: - lib/gemini/types/interactions/params.ex (unused parameter structs) - lib/gemini/types/live.ex (unused Live API types) - test/gemini/auth/vertex_strategy_test_old.exs (stale test file) Test updates: - gemini_strategy_test.exs now tests headers/1 interface - auth_test.exs uses build_headers/2 instead of authenticate/2 - Removed tests for deleted would_exceed_budget?/3 function
1 parent 0536dee commit 526eb11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+635
-1381
lines changed

CHANGELOG.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,72 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.8] - 2025-12-29
9+
10+
### Added
11+
- **Paid Tier 3 Rate Limit Profile**: New `:paid_tier_3` profile for maximum throughput workloads
12+
- Qualification: >$1,000 total spend + 30 days since payment
13+
- Settings: 30 max concurrency, 4M token budget, 100ms backoff, 50 adaptive ceiling
14+
- Added corresponding test coverage in `rate_limiter_test.exs`
15+
16+
- **Use-Case Model Aliases**: Implemented `Gemini.Config.model_for_use_case/2` and related functions
17+
- `model_for_use_case/2`: Resolve use-case atoms to model strings with optional API validation
18+
- `use_case_token_budget/1`: Get recommended token budget for a use-case
19+
- `resolved_use_case_models/0`: Get all use-case to model mappings
20+
- `available_use_cases/0`: List available use-case aliases
21+
- Use cases: `:cache_context` (gemini-2.5-flash), `:report_section` (gemini-2.5-pro), `:fast_path` (gemini-2.5-flash-lite)
22+
23+
- **Shared Utility Modules**: Consolidated duplicated helpers into dedicated modules
24+
- `Gemini.Utils.MapHelpers`: `maybe_put/3`, `maybe_put_non_empty/3`, `maybe_put_non_zero/3`, `build_paginated_path/2`, `add_query_param/3`
25+
- `Gemini.Utils.PollingHelpers`: `timed_out?/2`, `maybe_add/3`
26+
27+
### Changed
28+
- **Rate Limit Documentation**: Updated to reflect current Google API tier structure
29+
- Added tier qualifications table (Free, Tier 1-3 with spending thresholds)
30+
- Documented that rate limits are per-project (not per-API key)
31+
- Added note that RPD quotas reset at midnight Pacific time
32+
- Added links to AI Studio for viewing actual model-specific limits
33+
- Removed hardcoded RPM/TPM values (vary by model)
34+
35+
- **Unified Streaming Architecture**: Removed redundant streaming managers
36+
- `UnifiedManager` is now the canonical implementation
37+
- Deleted legacy `Manager` and `ManagerV2` modules
38+
- Updated examples and tests to use unified architecture
39+
40+
- **Authentication Interface**: Renamed `strategy/1` to `Gemini.Auth.get_strategy/1`
41+
42+
### Removed
43+
- **Dead Code Cleanup**: Removed unused functions and modules discovered during codebase audit
44+
- `Gemini.Auth`: `authenticate/2`, `base_url/2` delegators
45+
- `Gemini.Auth.GeminiStrategy`: `authenticate/1` (headers/1 is the actual interface)
46+
- `Gemini.Auth.MultiAuthCoordinator`: Unused struct definition
47+
- `Gemini.Client`: `stream_post/2`, `stream_post_with_auth/4` (moved to HTTPStreaming)
48+
- `Gemini.Client.HTTP`: All SSE streaming functions (module now focused on regular HTTP)
49+
- `Gemini.Config`: `models/0`, `use_case_models/0`, `use_case_token_minima/0`
50+
- `Gemini.RateLimiter.ConcurrencyGate`: `reset/1`
51+
- `Gemini.RateLimiter.Config`: `adaptive_enabled?/1`, `profile_config/1`
52+
- `Gemini.RateLimiter.State`: `would_exceed_budget?/3`
53+
- `Gemini.Streaming.ToolOrchestrator`: `subscribe/2`, `stop/1`, `cleanup_streams/1`
54+
55+
- **Deleted Files**:
56+
- `lib/gemini/types/interactions/params.ex` (unused parameter structs)
57+
- `lib/gemini/types/live.ex` (unused Live API types)
58+
- `lib/gemini/apis/generate.ex` (migrated to Coordinator)
59+
- `lib/gemini/live/session.ex` and `lib/gemini/live/message.ex` (experimental modules)
60+
- `test/gemini/auth/vertex_strategy_test_old.exs` (stale test file)
61+
- `oldDocs/` directory (obsolete design specifications)
62+
63+
### Technical
64+
- 13 modules updated to use shared `MapHelpers` and `PollingHelpers` utilities
65+
- Removed all `__test_` prefixed helper functions from ContextCache, Coordinator, and Videos modules
66+
- Tests now focus on public module interfaces instead of internal helpers
67+
- Function extraction and modularization across API modules
68+
69+
### Notes
70+
- All changes maintain backward compatibility for public API
71+
- Zero compilation warnings maintained
72+
- Documentation generates without warnings
73+
874
## [0.8.7] - 2025-12-27
975

1076
### Changed

docs/guides/rate_limiting.md

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,35 @@ end
8686

8787
## Quick Start
8888

89-
Choose a profile matching your Google Cloud tier:
89+
Choose a profile matching your Google Cloud tier. Rate limits are **per-project** (not per-API key)
90+
and vary by model. View your actual limits in [AI Studio](https://aistudio.google.com/usage?timeRange=last-28-days&tab=rate-limit).
9091

91-
| Profile | Best For | RPM | TPM | Token Budget |
92-
|---------|----------|-----|-----|--------------|
93-
| `:free_tier` | Development, testing | 15 | 1M | 32,000 |
94-
| `:paid_tier_1` | Standard production | 500 | 4M | 1,000,000 |
95-
| `:paid_tier_2` | High throughput | 1000 | 8M | 2,000,000 |
92+
### Tier Qualifications
93+
94+
| Tier | Qualification |
95+
|------|---------------|
96+
| Free | Users in [eligible countries](https://ai.google.dev/gemini-api/docs/available-regions) |
97+
| Tier 1 | Billing account [linked to project](https://ai.google.dev/gemini-api/docs/billing#enable-cloud-billing) |
98+
| Tier 2 | >$250 total spend + 30 days since payment |
99+
| Tier 3 | >$1,000 total spend + 30 days since payment |
100+
101+
### Profile Settings
102+
103+
| Profile | Best For | Token Budget |
104+
|---------|----------|--------------|
105+
| `:free_tier` | Development, testing | 32,000 |
106+
| `:paid_tier_1` | Standard production | 1,000,000 |
107+
| `:paid_tier_2` | High throughput | 2,000,000 |
108+
| `:paid_tier_3` | Maximum throughput | 4,000,000 |
96109

97110
```elixir
98111
# Select your tier
99112
config :gemini_ex, :rate_limiter, profile: :paid_tier_1
100113
```
101114

102-
> **Default behavior:** If you don’t choose a profile, `:prod` is used (`token_budget_per_window: 500_000`, `window_duration_ms: 60_000`). The base fallback defaults are 32,000/60s and are used by `:custom` unless overridden. Set `token_budget_per_window: nil` if you need the pre-0.6.1 “unlimited” budgeting behavior.
115+
> **Default behavior:** If you don't choose a profile, `:prod` is used (`token_budget_per_window: 500_000`, `window_duration_ms: 60_000`). The base fallback defaults are 32,000/60s and are used by `:custom` unless overridden. Set `token_budget_per_window: nil` if you need the pre-0.6.1 "unlimited" budgeting behavior.
116+
117+
> **Note:** Requests per day (RPD) quotas reset at midnight Pacific time.
103118
104119
## Profiles
105120

@@ -110,7 +125,7 @@ appropriate limits for your Google Cloud plan.
110125

111126
#### Free Tier (`:free_tier`)
112127

113-
Conservative settings for Google's free tier (15 RPM / 1M TPM):
128+
Conservative settings for Google's free tier:
114129

115130
```elixir
116131
config :gemini_ex, :rate_limiter, profile: :free_tier
@@ -126,7 +141,7 @@ config :gemini_ex, :rate_limiter, profile: :free_tier
126141

127142
#### Paid Tier 1 (`:paid_tier_1`)
128143

129-
Standard production settings for Tier 1 plans (500 RPM / 4M TPM):
144+
Standard production settings for Tier 1 (billing account linked):
130145

131146
```elixir
132147
config :gemini_ex, :rate_limiter, profile: :paid_tier_1
@@ -142,7 +157,7 @@ config :gemini_ex, :rate_limiter, profile: :paid_tier_1
142157

143158
#### Paid Tier 2 (`:paid_tier_2`)
144159

145-
High throughput settings for Tier 2 plans (1000 RPM / 8M TPM):
160+
High throughput settings for Tier 2 (>$250 total spend):
146161

147162
```elixir
148163
config :gemini_ex, :rate_limiter, profile: :paid_tier_2
@@ -156,6 +171,22 @@ config :gemini_ex, :rate_limiter, profile: :paid_tier_2
156171
# adaptive_ceiling: 30
157172
```
158173

174+
#### Paid Tier 3 (`:paid_tier_3`)
175+
176+
Maximum throughput settings for Tier 3 (>$1,000 total spend):
177+
178+
```elixir
179+
config :gemini_ex, :rate_limiter, profile: :paid_tier_3
180+
181+
# Equivalent to:
182+
# max_concurrency_per_model: 30
183+
# max_attempts: 2
184+
# base_backoff_ms: 100
185+
# token_budget_per_window: 4_000_000
186+
# adaptive_concurrency: true
187+
# adaptive_ceiling: 50
188+
```
189+
159190
### Legacy Profiles
160191

161192
#### Development Profile (`:dev`)
@@ -299,9 +330,10 @@ and is used for proactive budget checking.
299330

300331
Each profile includes a default token budget:
301332

302-
- `:free_tier` - 32,000 tokens per minute (~3% of 1M TPM)
303-
- `:paid_tier_1` - 1,000,000 tokens per minute (25% of 4M TPM)
304-
- `:paid_tier_2` - 2,000,000 tokens per minute (25% of 8M TPM)
333+
- `:free_tier` - 32,000 tokens per minute
334+
- `:paid_tier_1` - 1,000,000 tokens per minute
335+
- `:paid_tier_2` - 2,000,000 tokens per minute
336+
- `:paid_tier_3` - 4,000,000 tokens per minute
305337
- `:prod` - 500,000 tokens per minute
306338
- `:dev` - 16,000 tokens per minute
307339

lib/gemini/apis/batches.ex

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ defmodule Gemini.APIs.Batches do
6262
alias Gemini.Config
6363
alias Gemini.Types.{BatchJob, ListBatchJobsResponse}
6464

65+
import Gemini.Utils.PollingHelpers, only: [timed_out?: 2]
66+
import Gemini.Utils.MapHelpers, only: [build_paginated_path: 2]
67+
6568
@type create_opts :: [
6669
{:display_name, String.t()}
6770
| {:file_name, String.t()}
@@ -461,32 +464,7 @@ defmodule Gemini.APIs.Batches do
461464
defp normalize_batch_path("batchPredictionJobs/" <> _ = name), do: name
462465
defp normalize_batch_path(name), do: "batches/#{name}"
463466

464-
defp build_list_path(opts) do
465-
query_params = []
466-
467-
query_params =
468-
case Keyword.get(opts, :page_size) do
469-
nil -> query_params
470-
size -> [{"pageSize", size} | query_params]
471-
end
472-
473-
query_params =
474-
case Keyword.get(opts, :page_token) do
475-
nil -> query_params
476-
token -> [{"pageToken", token} | query_params]
477-
end
478-
479-
query_params =
480-
case Keyword.get(opts, :filter) do
481-
nil -> query_params
482-
filter -> [{"filter", filter} | query_params]
483-
end
484-
485-
case query_params do
486-
[] -> "batches"
487-
params -> "batches?" <> URI.encode_query(params)
488-
end
489-
end
467+
defp build_list_path(opts), do: build_paginated_path("batches", opts)
490468

491469
defp collect_all_batches(opts, acc) do
492470
case list(opts) do
@@ -527,8 +505,4 @@ defmodule Gemini.APIs.Batches do
527505

528506
defp maybe_report_progress(nil, _batch), do: :ok
529507
defp maybe_report_progress(callback, batch), do: callback.(batch)
530-
531-
defp timed_out?(start_time, timeout) do
532-
System.monotonic_time(:millisecond) - start_time >= timeout
533-
end
534508
end

lib/gemini/apis/documents.ex

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ defmodule Gemini.APIs.Documents do
4141
alias Gemini.Client.HTTP
4242
alias Gemini.Types.{Document, ListDocumentsResponse}
4343

44+
import Gemini.Utils.PollingHelpers, only: [timed_out?: 2]
45+
import Gemini.Utils.MapHelpers, only: [build_paginated_path: 2, add_query_param: 3]
46+
4447
@type list_opts :: [
4548
{:page_size, pos_integer()}
4649
| {:page_token, String.t()}
@@ -225,16 +228,7 @@ defmodule Gemini.APIs.Documents do
225228
"ragStores/#{store_name}/documents"
226229
end
227230

228-
query_params =
229-
[]
230-
|> maybe_add_param("pageSize", Keyword.get(opts, :page_size))
231-
|> maybe_add_param("pageToken", Keyword.get(opts, :page_token))
232-
|> maybe_add_param("filter", Keyword.get(opts, :filter))
233-
234-
case query_params do
235-
[] -> base
236-
params -> base <> "?" <> URI.encode_query(params)
237-
end
231+
build_paginated_path(base, opts)
238232
end
239233

240234
defp collect_all_documents(store_name, opts, acc) do
@@ -261,9 +255,6 @@ defmodule Gemini.APIs.Documents do
261255
end
262256
end
263257

264-
defp maybe_add_param(params, _key, nil), do: params
265-
defp maybe_add_param(params, key, value), do: [{key, value} | params]
266-
267258
defp maybe_report_status(nil, _doc), do: :ok
268259
defp maybe_report_status(callback, doc), do: callback.(doc)
269260

@@ -316,10 +307,6 @@ defmodule Gemini.APIs.Documents do
316307

317308
defp handle_document_state(%{state: state}, _name, _opts, _poll, _timeout, _start, _cb),
318309
do: {:error, {:unknown_state, state}}
319-
320-
defp timed_out?(start_time, timeout) do
321-
System.monotonic_time(:millisecond) - start_time >= timeout
322-
end
323310
end
324311

325312
defmodule Gemini.APIs.RagStores do
@@ -355,6 +342,8 @@ defmodule Gemini.APIs.RagStores do
355342
alias Gemini.Client.HTTP
356343
alias Gemini.Types.{ListRagStoresResponse, RagStore}
357344

345+
import Gemini.Utils.MapHelpers, only: [maybe_put: 3, build_paginated_path: 2]
346+
358347
@type list_opts :: [
359348
{:page_size, pos_integer()}
360349
| {:page_token, String.t()}
@@ -500,26 +489,7 @@ defmodule Gemini.APIs.RagStores do
500489
defp normalize_store_path("ragStores/" <> _ = name), do: name
501490
defp normalize_store_path(name), do: "ragStores/#{name}"
502491

503-
defp build_list_path(opts) do
504-
query_params = []
505-
506-
query_params =
507-
case Keyword.get(opts, :page_size) do
508-
nil -> query_params
509-
size -> [{"pageSize", size} | query_params]
510-
end
511-
512-
query_params =
513-
case Keyword.get(opts, :page_token) do
514-
nil -> query_params
515-
token -> [{"pageToken", token} | query_params]
516-
end
517-
518-
case query_params do
519-
[] -> "ragStores"
520-
params -> "ragStores?" <> URI.encode_query(params)
521-
end
522-
end
492+
defp build_list_path(opts), do: build_paginated_path("ragStores", opts)
523493

524494
defp collect_all_stores(opts, acc) do
525495
case list(opts) do
@@ -533,7 +503,4 @@ defmodule Gemini.APIs.RagStores do
533503
{:error, reason}
534504
end
535505
end
536-
537-
defp maybe_put(map, _key, nil), do: map
538-
defp maybe_put(map, key, value), do: Map.put(map, key, value)
539506
end

lib/gemini/apis/file_search_stores.ex

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ defmodule Gemini.APIs.FileSearchStores do
101101
ListFileSearchStoresResponse
102102
}
103103

104+
import Gemini.Utils.PollingHelpers, only: [timed_out?: 2]
105+
import Gemini.Utils.MapHelpers, only: [build_paginated_path: 2]
106+
104107
@type create_opts :: [
105108
{:auth, :gemini | :vertex_ai}
106109
]
@@ -521,35 +524,7 @@ defmodule Gemini.APIs.FileSearchStores do
521524
end
522525
end
523526

524-
defp build_list_path(opts) do
525-
base = "fileSearchStores"
526-
query_params = []
527-
528-
query_params =
529-
case Keyword.get(opts, :page_size) do
530-
nil -> query_params
531-
size -> [{"pageSize", size} | query_params]
532-
end
533-
534-
query_params =
535-
case Keyword.get(opts, :page_token) do
536-
nil -> query_params
537-
token -> [{"pageToken", token} | query_params]
538-
end
539-
540-
case query_params do
541-
[] ->
542-
base
543-
544-
params ->
545-
query_string =
546-
Enum.map_join(params, "&", fn {k, v} ->
547-
"#{k}=#{URI.encode_www_form(to_string(v))}"
548-
end)
549-
550-
"#{base}?#{query_string}"
551-
end
552-
end
527+
defp build_list_path(opts), do: build_paginated_path("fileSearchStores", opts)
553528

554529
defp collect_all_stores(opts, acc) do
555530
case list(opts) do
@@ -647,8 +622,4 @@ defmodule Gemini.APIs.FileSearchStores do
647622

648623
defp maybe_report_status(nil, _resource), do: :ok
649624
defp maybe_report_status(callback, resource), do: callback.(resource)
650-
651-
defp timed_out?(start_time, timeout) do
652-
System.monotonic_time(:millisecond) - start_time >= timeout
653-
end
654625
end

0 commit comments

Comments
 (0)