evidor 1.10.6.dev1__tar.gz → 1.10.7.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/CHANGELOG.md +11 -0
  2. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/PKG-INFO +12 -7
  3. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/README.md +11 -6
  4. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/pyproject.toml +1 -1
  5. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/gemini/builder.py +33 -13
  6. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/gemini/provider.py +1 -13
  7. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/models/thinking.py +13 -8
  8. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_providers.py +33 -9
  9. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_thinking.py +27 -23
  10. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/.github/workflows/release.yml +0 -0
  11. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/.gitignore +0 -0
  12. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/AGENTS.md +0 -0
  13. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/CODE_OF_CONDUCT.md +0 -0
  14. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/LICENSE +0 -0
  15. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/requirements.txt +0 -0
  16. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/__init__.py +0 -0
  17. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/__init__.py +0 -0
  18. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/core/__init__.py +0 -0
  19. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/core/agent.py +0 -0
  20. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/core/context.py +0 -0
  21. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/core/runner.py +0 -0
  22. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/core/telemetry_bridge.py +0 -0
  23. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/__init__.py +0 -0
  24. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/anthropic/__init__.py +0 -0
  25. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/anthropic/builder.py +0 -0
  26. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/anthropic/parser.py +0 -0
  27. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/anthropic/provider.py +0 -0
  28. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/anthropic/stream.py +0 -0
  29. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/base.py +0 -0
  30. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/client_pool.py +0 -0
  31. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/gemini/__init__.py +0 -0
  32. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/gemini/parser.py +0 -0
  33. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/gemini/stream.py +0 -0
  34. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/openai/__init__.py +0 -0
  35. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/openai/builder.py +0 -0
  36. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/openai/parser.py +0 -0
  37. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/openai/provider.py +0 -0
  38. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/agent/providers/openai/stream.py +0 -0
  39. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/constants.py +0 -0
  40. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/errors.py +0 -0
  41. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/mcp/__init__.py +0 -0
  42. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/mcp/client.py +0 -0
  43. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/mcp/config.py +0 -0
  44. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/mcp/runner.py +0 -0
  45. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/mcp/session.py +0 -0
  46. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/models/__init__.py +0 -0
  47. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/models/generation.py +0 -0
  48. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/models/streaming.py +0 -0
  49. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/__init__.py +0 -0
  50. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/adapters/__init__.py +0 -0
  51. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/adapters/langfuse.py +0 -0
  52. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/adapters/opentelemetry.py +0 -0
  53. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/adapters/phoenix.py +0 -0
  54. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/adapters/prometheus.py +0 -0
  55. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/context.py +0 -0
  56. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/events.py +0 -0
  57. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/runtime.py +0 -0
  58. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/semconv.py +0 -0
  59. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/telemetry/sink.py +0 -0
  60. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/__init__.py +0 -0
  61. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/core/__init__.py +0 -0
  62. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/core/tool_executor.py +0 -0
  63. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/core/tools.py +0 -0
  64. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/core/utils.py +0 -0
  65. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/__init__.py +0 -0
  66. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/calculator.py +0 -0
  67. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/evidence_store.py +0 -0
  68. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/filesystem/__init__.py +0 -0
  69. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/filesystem/_common.py +0 -0
  70. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/filesystem/list_files.py +0 -0
  71. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/filesystem/mutate_files.py +0 -0
  72. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/filesystem/read_file.py +0 -0
  73. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/filesystem/search_files.py +0 -0
  74. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/get_current_time.py +0 -0
  75. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/read_webpage.py +0 -0
  76. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/__init__.py +0 -0
  77. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/_base.py +0 -0
  78. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/http.py +0 -0
  79. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/providers/__init__.py +0 -0
  80. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/providers/brave.py +0 -0
  81. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/providers/exa.py +0 -0
  82. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/providers/tavily.py +0 -0
  83. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/tools/native/web_search/tool.py +0 -0
  84. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/utils/__init__.py +0 -0
  85. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/utils/execution.py +0 -0
  86. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/src/evidor/utils/schema.py +0 -0
  87. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_agent.py +0 -0
  88. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_builtin_tools.py +0 -0
  89. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_conversation.py +0 -0
  90. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_filesystem_tools.py +0 -0
  91. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_langfuse_adapter.py +0 -0
  92. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_mcp.py +0 -0
  93. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_models.py +0 -0
  94. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_native_tools.py +0 -0
  95. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_opentelemetry_adapter.py +0 -0
  96. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_phoenix_adapter.py +0 -0
  97. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_prometheus_adapter.py +0 -0
  98. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_streaming.py +0 -0
  99. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_telemetry_events.py +0 -0
  100. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_telemetry_integration.py +0 -0
  101. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_tools.py +0 -0
  102. {evidor-1.10.6.dev1 → evidor-1.10.7.dev1}/tests/test_web_search.py +0 -0
@@ -2,6 +2,17 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.10.7-dev.1 (2026-11-02)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **gemini**: Fixing thinking related issues
10
+ ([`4451ea1`](https://github.com/vedangiitb/evidor-core/commit/4451ea1d3d5039fd0cc276b9f6b0f6ceb88c5a92))
11
+
12
+
13
+ ## v1.10.6 (2026-10-31)
14
+
15
+
5
16
  ## v1.10.6-dev.1 (2026-10-30)
6
17
 
7
18
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evidor
3
- Version: 1.10.6-dev.1
3
+ Version: 1.10.7-dev.1
4
4
  Summary: A small, provider-agnostic harness for LLM input and output.
5
5
  Project-URL: Homepage, https://github.com/evidor/evidor-core
6
6
  Project-URL: Repository, https://github.com/evidor/evidor-core
@@ -547,16 +547,21 @@ provider = AnthropicProvider(
547
547
  thinking=AnthropicThinkingConfig(budget_tokens=4096),
548
548
  )
549
549
 
550
- # 3. Gemini: Configure thinking level ('minimal', 'low', 'medium', 'high') and summaries ('auto', 'none')
551
- # Shorthand string (Gemini 3.x thinking_level):
550
+ # 3. Gemini: 2 modes for thinking configuration
551
+ # Mode 1: Thinking level string ('minimal', 'low', 'medium', 'high'):
552
552
  provider = GeminiProvider(model="gemini-3.8-flash", thinking="high")
553
- # Shorthand bool (False disables thinking; True enables default thinking):
554
- provider = GeminiProvider(model="gemini-3.8-flash", thinking=False)
555
- # Or via typed config:
553
+
554
+ # Mode 2: Object supporting thinking_level, thinking_summaries ('auto', 'none'), or thinking_budget:
556
555
  provider = GeminiProvider(
557
556
  model="gemini-3.8-flash",
558
557
  thinking=GeminiThinkingConfig(thinking_level="high", thinking_summaries="auto"),
559
558
  )
559
+
560
+ # Earlier models supporting numeric thinking_budget can also be configured via GeminiThinkingConfig:
561
+ provider = GeminiProvider(
562
+ model="gemini-2.5-flash",
563
+ thinking=GeminiThinkingConfig(thinking_budget=2048),
564
+ )
560
565
  ```
561
566
 
562
567
  #### Inspecting Thinking Steps & Reasoning Tokens
@@ -1286,7 +1291,7 @@ Supported providers, example models, and reasoning/thinking support:
1286
1291
  | --- | --- | --- | --- | --- |
1287
1292
  | `OpenAIProvider` | `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna`, `gpt-4.1-mini` | `reasoning_effort` (`"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"`) | `OPENAI_API_KEY` | `evidor[openai]` |
1288
1293
  | `AnthropicProvider` | `claude-3-7-sonnet` | Extended budget (`budget_tokens >= 1024`) or adaptive | `ANTHROPIC_API_KEY` | `evidor[anthropic]` |
1289
- | `GeminiProvider` | `gemini-3.8-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-pro-preview` | `thinking_level` (`"minimal"`, `"low"`, `"medium"`, `"high"`), `thinking_summaries` (`"auto"`, `"none"`) | `GEMINI_API_KEY` | `evidor[gemini]` |
1294
+ | `GeminiProvider` | `gemini-3.8-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-pro-preview` | `thinking_level` (`"minimal"`, `"low"`, `"medium"`, `"high"`), `thinking_summaries` (`"auto"`, `"none"`), or `thinking_budget` | `GEMINI_API_KEY` | `evidor[gemini]` |
1290
1295
 
1291
1296
  API keys can be supplied explicitly via the `api_key` constructor argument or read automatically from environment variables. Custom base URLs for OpenAI-compatible providers (such as DeepSeek) can be supplied via the `base_url` constructor argument or `OPENAI_BASE_URL` environment variable:
1292
1297
 
@@ -460,16 +460,21 @@ provider = AnthropicProvider(
460
460
  thinking=AnthropicThinkingConfig(budget_tokens=4096),
461
461
  )
462
462
 
463
- # 3. Gemini: Configure thinking level ('minimal', 'low', 'medium', 'high') and summaries ('auto', 'none')
464
- # Shorthand string (Gemini 3.x thinking_level):
463
+ # 3. Gemini: 2 modes for thinking configuration
464
+ # Mode 1: Thinking level string ('minimal', 'low', 'medium', 'high'):
465
465
  provider = GeminiProvider(model="gemini-3.8-flash", thinking="high")
466
- # Shorthand bool (False disables thinking; True enables default thinking):
467
- provider = GeminiProvider(model="gemini-3.8-flash", thinking=False)
468
- # Or via typed config:
466
+
467
+ # Mode 2: Object supporting thinking_level, thinking_summaries ('auto', 'none'), or thinking_budget:
469
468
  provider = GeminiProvider(
470
469
  model="gemini-3.8-flash",
471
470
  thinking=GeminiThinkingConfig(thinking_level="high", thinking_summaries="auto"),
472
471
  )
472
+
473
+ # Earlier models supporting numeric thinking_budget can also be configured via GeminiThinkingConfig:
474
+ provider = GeminiProvider(
475
+ model="gemini-2.5-flash",
476
+ thinking=GeminiThinkingConfig(thinking_budget=2048),
477
+ )
473
478
  ```
474
479
 
475
480
  #### Inspecting Thinking Steps & Reasoning Tokens
@@ -1199,7 +1204,7 @@ Supported providers, example models, and reasoning/thinking support:
1199
1204
  | --- | --- | --- | --- | --- |
1200
1205
  | `OpenAIProvider` | `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna`, `gpt-4.1-mini` | `reasoning_effort` (`"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"`) | `OPENAI_API_KEY` | `evidor[openai]` |
1201
1206
  | `AnthropicProvider` | `claude-3-7-sonnet` | Extended budget (`budget_tokens >= 1024`) or adaptive | `ANTHROPIC_API_KEY` | `evidor[anthropic]` |
1202
- | `GeminiProvider` | `gemini-3.8-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-pro-preview` | `thinking_level` (`"minimal"`, `"low"`, `"medium"`, `"high"`), `thinking_summaries` (`"auto"`, `"none"`) | `GEMINI_API_KEY` | `evidor[gemini]` |
1207
+ | `GeminiProvider` | `gemini-3.8-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-pro-preview` | `thinking_level` (`"minimal"`, `"low"`, `"medium"`, `"high"`), `thinking_summaries` (`"auto"`, `"none"`), or `thinking_budget` | `GEMINI_API_KEY` | `evidor[gemini]` |
1203
1208
 
1204
1209
  API keys can be supplied explicitly via the `api_key` constructor argument or read automatically from environment variables. Custom base URLs for OpenAI-compatible providers (such as DeepSeek) can be supplied via the `base_url` constructor argument or `OPENAI_BASE_URL` environment variable:
1205
1210
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "evidor"
7
- version = "1.10.6-dev.1"
7
+ version = "1.10.7-dev.1"
8
8
  description = "A small, provider-agnostic harness for LLM input and output."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -138,7 +138,12 @@ class GeminiPayloadBuilder:
138
138
 
139
139
  return cleaned
140
140
 
141
- def build(self, request: GenerationRequest, types: Any = None) -> dict[str, Any]:
141
+ def build(
142
+ self,
143
+ request: GenerationRequest,
144
+ types: Any = None,
145
+ thinking: Any = None,
146
+ ) -> dict[str, Any]:
142
147
  """Construct the full request dictionary for Gemini Interactions API."""
143
148
  system_prompt = extract_system_prompt(request)
144
149
  input_steps = self.format_input_steps(request.messages)
@@ -167,19 +172,34 @@ class GeminiPayloadBuilder:
167
172
  for t in sorted_tools
168
173
  ]
169
174
 
170
- if self.thinking is not None:
175
+ active_thinking = thinking if thinking is not None else self.thinking
176
+ if active_thinking and isinstance(active_thinking, str):
177
+ payload["generation_config"] = {"thinking_level": active_thinking}
178
+ elif active_thinking and not isinstance(active_thinking, bool):
171
179
  gen_config: dict[str, Any] = {}
172
- if not getattr(self.thinking, "enabled", True):
173
- gen_config["thinking_level"] = "minimal"
174
- gen_config["thinking_summaries"] = "none"
175
- else:
176
- thinking_level = getattr(self.thinking, "thinking_level", None)
177
- if thinking_level:
178
- gen_config["thinking_level"] = thinking_level
179
-
180
- thinking_summaries = getattr(self.thinking, "thinking_summaries", None)
181
- if thinking_summaries:
182
- gen_config["thinking_summaries"] = thinking_summaries
180
+ thinking_level = (
181
+ active_thinking.get("thinking_level")
182
+ if isinstance(active_thinking, dict)
183
+ else getattr(active_thinking, "thinking_level", None)
184
+ )
185
+ if isinstance(thinking_level, str):
186
+ gen_config["thinking_level"] = thinking_level
187
+
188
+ thinking_summaries = (
189
+ active_thinking.get("thinking_summaries")
190
+ if isinstance(active_thinking, dict)
191
+ else getattr(active_thinking, "thinking_summaries", None)
192
+ )
193
+ if isinstance(thinking_summaries, str):
194
+ gen_config["thinking_summaries"] = thinking_summaries
195
+
196
+ thinking_budget = (
197
+ active_thinking.get("thinking_budget")
198
+ if isinstance(active_thinking, dict)
199
+ else getattr(active_thinking, "thinking_budget", None)
200
+ )
201
+ if thinking_budget is not None and not isinstance(thinking_budget, bool):
202
+ gen_config["thinking_budget"] = thinking_budget
183
203
 
184
204
  if gen_config:
185
205
  payload["generation_config"] = gen_config
@@ -32,19 +32,7 @@ class GeminiProvider(BaseProvider):
32
32
  cached_content: str | None = None,
33
33
  thinking: Any = None,
34
34
  ) -> None:
35
- if thinking is not None:
36
- from evidor.models import GeminiThinkingConfig
37
-
38
- thinking_cfg: GeminiThinkingConfig | None = (
39
- thinking
40
- if isinstance(thinking, GeminiThinkingConfig)
41
- else GeminiThinkingConfig.from_value(thinking)
42
- )
43
- thinking_cfg.validate()
44
- else:
45
- thinking_cfg = None
46
-
47
- super().__init__(model=model, api_key=api_key, thinking=thinking_cfg)
35
+ super().__init__(model=model, api_key=api_key, thinking=thinking)
48
36
  self._cached_content = cached_content
49
37
  self._client: Any = None
50
38
  self._types: Any = None
@@ -67,11 +67,12 @@ class AnthropicThinkingConfig(ThinkingConfig):
67
67
 
68
68
 
69
69
  @dataclass(frozen=True, slots=True)
70
- class GeminiThinkingConfig(ThinkingConfig):
70
+ class GeminiThinkingConfig:
71
71
  """Configuration for Google Gemini thinking models in the Interactions API."""
72
72
 
73
73
  thinking_level: str | None = None # Supported in Gemini 3.x Interactions API: 'minimal', 'low', 'medium', 'high'
74
- thinking_summaries: str | None = "auto" # Supported in Gemini Interactions API: 'auto', 'none'
74
+ thinking_summaries: str | None = None # Supported in Gemini Interactions API: 'auto', 'none'
75
+ thinking_budget: int | None = None # Supported in earlier Gemini models (e.g. Gemini 2.5)
75
76
 
76
77
  def validate(self) -> None:
77
78
  pass
@@ -81,15 +82,19 @@ class GeminiThinkingConfig(ThinkingConfig):
81
82
  if isinstance(value, GeminiThinkingConfig):
82
83
  return value
83
84
  if isinstance(value, bool):
84
- if value:
85
- return cls(enabled=True, thinking_summaries="auto")
86
- return cls(enabled=False, thinking_level="minimal", thinking_summaries="none")
85
+ raise TypeError(
86
+ "Cannot coerce bool to GeminiThinkingConfig. "
87
+ "Gemini Interactions API supports thinking_level (e.g. 'minimal', 'low', 'medium', 'high'), "
88
+ "thinking_summaries ('auto', 'none'), or thinking_budget (int)."
89
+ )
90
+ if isinstance(value, int):
91
+ return cls(thinking_budget=value)
87
92
  if isinstance(value, str):
88
- return cls(enabled=True, thinking_level=value, thinking_summaries="auto")
93
+ return cls(thinking_level=value)
89
94
  raise TypeError(
90
95
  f"Cannot coerce {type(value).__name__} to GeminiThinkingConfig. "
91
- "Gemini Interactions API supports thinking_level (e.g. 'minimal', 'low', 'medium', 'high') "
92
- "and thinking_summaries ('auto', 'none'). Numeric budgets are not supported."
96
+ "Gemini Interactions API supports thinking_level (e.g. 'minimal', 'low', 'medium', 'high'), "
97
+ "thinking_summaries ('auto', 'none'), or thinking_budget (int)."
93
98
  )
94
99
 
95
100
 
@@ -1414,7 +1414,17 @@ def test_gemini_payload_builder_thinking_config() -> None:
1414
1414
 
1415
1415
  req = GenerationRequest(prompt="hello")
1416
1416
 
1417
- # 1. Arbitrary / future thinking_level (no model check, no whitelist)
1417
+ # Mode 1: thinking="..." (user configures thinking as a string)
1418
+ b_str = GeminiPayloadBuilder(
1419
+ model="gemini-3.8-flash",
1420
+ thinking="high",
1421
+ )
1422
+ p_str = b_str.build(req)
1423
+ assert p_str["generation_config"] == {
1424
+ "thinking_level": "high",
1425
+ }
1426
+
1427
+ # Mode 2: Arbitrary / future thinking_level with thinking_summaries
1418
1428
  b_custom = GeminiPayloadBuilder(
1419
1429
  model="gemini-3.8-flash",
1420
1430
  thinking=GeminiThinkingConfig(thinking_level="experimental_deep", thinking_summaries="auto"),
@@ -1425,7 +1435,7 @@ def test_gemini_payload_builder_thinking_config() -> None:
1425
1435
  "thinking_summaries": "auto",
1426
1436
  }
1427
1437
 
1428
- # 2. thinking_summaries="none"
1438
+ # Mode 2: thinking_summaries="none"
1429
1439
  b_no_thoughts = GeminiPayloadBuilder(
1430
1440
  model="gemini-3.8-flash",
1431
1441
  thinking=GeminiThinkingConfig(thinking_level="high", thinking_summaries="none"),
@@ -1436,17 +1446,31 @@ def test_gemini_payload_builder_thinking_config() -> None:
1436
1446
  "thinking_summaries": "none",
1437
1447
  }
1438
1448
 
1439
- # 3. enabled=False disabled thinking -> minimal + none
1440
- b_off = GeminiPayloadBuilder(
1449
+ # Mode 2: thinking_budget supported without error on client side
1450
+ b_budget = GeminiPayloadBuilder(
1441
1451
  model="gemini-3.8-flash",
1442
- thinking=GeminiThinkingConfig(enabled=False),
1452
+ thinking=GeminiThinkingConfig(thinking_budget=2048),
1443
1453
  )
1444
- p_off = b_off.build(req)
1445
- assert p_off["generation_config"] == {
1446
- "thinking_level": "minimal",
1447
- "thinking_summaries": "none",
1454
+ p_budget = b_budget.build(req)
1455
+ assert p_budget["generation_config"] == {
1456
+ "thinking_budget": 2048,
1448
1457
  }
1449
1458
 
1459
+ # Don't handle for thinking=False
1460
+ b_false = GeminiPayloadBuilder(
1461
+ model="gemini-3.8-flash",
1462
+ thinking=False,
1463
+ )
1464
+ p_false = b_false.build(req)
1465
+ assert "generation_config" not in p_false
1466
+
1467
+ # build() handles thinking passed directly without needing it at init
1468
+ b_dynamic = GeminiPayloadBuilder(model="gemini-3.8-flash")
1469
+ p_dyn_str = b_dynamic.build(req, thinking="low")
1470
+ assert p_dyn_str["generation_config"] == {"thinking_level": "low"}
1471
+ p_dyn_obj = b_dynamic.build(req, thinking=GeminiThinkingConfig(thinking_budget=4096))
1472
+ assert p_dyn_obj["generation_config"] == {"thinking_budget": 4096}
1473
+
1450
1474
 
1451
1475
  def test_gemini_payload_builder_types_optional() -> None:
1452
1476
  from evidor.agent.providers.gemini.builder import GeminiPayloadBuilder
@@ -104,34 +104,30 @@ def test_gemini_thinking_config() -> None:
104
104
  cfg = GeminiThinkingConfig(thinking_level="high", thinking_summaries="auto")
105
105
  assert cfg.thinking_level == "high"
106
106
  assert cfg.thinking_summaries == "auto"
107
+ assert not hasattr(cfg, "enabled")
107
108
  cfg.validate()
108
109
 
109
110
  # Dynamic / None
110
111
  cfg_dyn = GeminiThinkingConfig(thinking_level=None)
111
112
  cfg_dyn.validate()
112
113
 
113
- # Disabled
114
- cfg_off = GeminiThinkingConfig(enabled=False)
115
- cfg_off.validate()
116
-
117
- # Coercion from int -> raises TypeError because Interactions API does not support numeric budgets
118
- with pytest.raises(TypeError, match="Numeric budgets are not supported"):
119
- GeminiThinkingConfig.from_value(1024)
120
-
121
- # Coercion from bool False -> minimal + none
122
- c_false = GeminiThinkingConfig.from_value(False)
123
- assert c_false.enabled is False
124
- assert c_false.thinking_level == "minimal"
125
- assert c_false.thinking_summaries == "none"
126
-
127
- # Coercion from bool True
128
- c_true = GeminiThinkingConfig.from_value(True)
129
- assert c_true.enabled is True
114
+ # Coercion from int -> thinking_budget supported
115
+ c_int = GeminiThinkingConfig.from_value(1024)
116
+ assert c_int.thinking_budget == 1024
117
+ assert not hasattr(c_int, "enabled")
130
118
 
131
119
  # Coercion from str -> thinking_level (Gemini 3.x)
132
120
  c_str = GeminiThinkingConfig.from_value("high")
133
- assert c_str.enabled is True
134
121
  assert c_str.thinking_level == "high"
122
+ assert not hasattr(c_str, "enabled")
123
+
124
+ # Enabled is not a parameter on GeminiThinkingConfig
125
+ with pytest.raises(TypeError):
126
+ GeminiThinkingConfig(enabled=True) # type: ignore[call-arg]
127
+
128
+ # Bool coercion is not supported on GeminiThinkingConfig
129
+ with pytest.raises(TypeError, match="Cannot coerce bool to GeminiThinkingConfig"):
130
+ GeminiThinkingConfig.from_value(False)
135
131
 
136
132
 
137
133
  # ==============================================================================
@@ -335,13 +331,20 @@ def test_gemini_provider_thinking_extraction_and_config(monkeypatch: pytest.Monk
335
331
  assert call_config["thinking_level"] == "medium"
336
332
  assert call_config["thinking_summaries"] == "auto"
337
333
 
338
- # Test Gemini 3.x thinking_level parameter
334
+ # Test Gemini 3.x thinking_level parameter (Mode 1: thinking="high")
339
335
  p_gemini_3 = GeminiProvider(model="gemini-3.8-flash", thinking="high")
340
- assert p_gemini_3.thinking.thinking_level == "high"
336
+ assert p_gemini_3.thinking == "high"
341
337
  res_3 = p_gemini_3.generate(GenerationRequest(prompt="Explain logic"))
342
338
  call_config_3 = mock_client.interactions.create.call_args.kwargs["generation_config"]
343
339
  assert call_config_3["thinking_level"] == "high"
344
- assert call_config_3["thinking_summaries"] == "auto"
340
+ assert "thinking_summaries" not in call_config_3
341
+
342
+ # Test Mode 2 with thinking_budget (earlier models)
343
+ p_gemini_budget = GeminiProvider(model="gemini-2.5-flash", thinking=GeminiThinkingConfig(thinking_budget=2048))
344
+ assert p_gemini_budget.thinking.thinking_budget == 2048
345
+ p_gemini_budget.generate(GenerationRequest(prompt="Explain logic"))
346
+ call_config_budget = mock_client.interactions.create.call_args.kwargs["generation_config"]
347
+ assert call_config_budget["thinking_budget"] == 2048
345
348
 
346
349
 
347
350
  def test_gemini_provider_disable_thinking(monkeypatch: pytest.MonkeyPatch) -> None:
@@ -358,8 +361,9 @@ def test_gemini_provider_disable_thinking(monkeypatch: pytest.MonkeyPatch) -> No
358
361
  provider = GeminiProvider(model="gemini-3.8-flash", thinking=False)
359
362
  provider.generate(GenerationRequest(prompt="Fast query"))
360
363
 
361
- call_config = mock_client.interactions.create.call_args.kwargs["generation_config"]
362
- assert call_config["thinking_level"] == "minimal"
364
+ # When thinking=False, it is not handled, so no generation_config is passed
365
+ call_kwargs = mock_client.interactions.create.call_args.kwargs
366
+ assert "generation_config" not in call_kwargs
363
367
 
364
368
 
365
369
  # ==============================================================================
File without changes
File without changes
File without changes