evidor 1.10.7.dev1__tar.gz → 1.11.0.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.
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/CHANGELOG.md +11 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/PKG-INFO +52 -32
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/README.md +51 -31
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/pyproject.toml +1 -1
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/__init__.py +2 -6
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/anthropic/builder.py +24 -14
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/anthropic/provider.py +8 -15
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/gemini/builder.py +19 -16
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/openai/builder.py +21 -10
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/openai/provider.py +1 -13
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/models/__init__.py +2 -6
- evidor-1.11.0.dev1/src/evidor/models/thinking.py +51 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_providers.py +18 -22
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_thinking.py +57 -108
- evidor-1.10.7.dev1/src/evidor/models/thinking.py +0 -102
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/.github/workflows/release.yml +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/.gitignore +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/AGENTS.md +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/CODE_OF_CONDUCT.md +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/LICENSE +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/requirements.txt +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/core/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/core/agent.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/core/context.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/core/runner.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/core/telemetry_bridge.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/anthropic/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/anthropic/parser.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/anthropic/stream.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/base.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/client_pool.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/gemini/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/gemini/parser.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/gemini/provider.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/gemini/stream.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/openai/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/openai/parser.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/agent/providers/openai/stream.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/constants.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/errors.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/mcp/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/mcp/client.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/mcp/config.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/mcp/runner.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/mcp/session.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/models/generation.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/models/streaming.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/adapters/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/adapters/langfuse.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/adapters/opentelemetry.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/adapters/phoenix.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/adapters/prometheus.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/context.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/events.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/runtime.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/semconv.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/telemetry/sink.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/core/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/core/tool_executor.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/core/tools.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/core/utils.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/calculator.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/evidence_store.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/filesystem/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/filesystem/_common.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/filesystem/list_files.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/filesystem/mutate_files.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/filesystem/read_file.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/filesystem/search_files.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/get_current_time.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/read_webpage.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/_base.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/http.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/providers/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/providers/brave.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/providers/exa.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/providers/tavily.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/tools/native/web_search/tool.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/utils/__init__.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/utils/execution.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/src/evidor/utils/schema.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_agent.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_builtin_tools.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_conversation.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_filesystem_tools.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_langfuse_adapter.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_mcp.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_models.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_native_tools.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_opentelemetry_adapter.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_phoenix_adapter.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_prometheus_adapter.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_streaming.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_telemetry_events.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_telemetry_integration.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_tools.py +0 -0
- {evidor-1.10.7.dev1 → evidor-1.11.0.dev1}/tests/test_web_search.py +0 -0
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.11.0-dev.1 (2026-11-03)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **agent**: Centralized thinking configs
|
|
10
|
+
([`4a05c49`](https://github.com/vedangiitb/evidor-core/commit/4a05c49bbae3852a10d9597c6c27c1d9cdd335af))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## v1.10.7 (2026-11-02)
|
|
14
|
+
|
|
15
|
+
|
|
5
16
|
## v1.10.7-dev.1 (2026-11-02)
|
|
6
17
|
|
|
7
18
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: evidor
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.11.0-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
|
|
@@ -443,7 +443,7 @@ For reasoning models (**OpenAI** `gpt-6-astra`, **Anthropic** `claude-3-7-sonnet
|
|
|
443
443
|
```python
|
|
444
444
|
from evidor import Agent, AnthropicProvider
|
|
445
445
|
|
|
446
|
-
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking=
|
|
446
|
+
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high"))
|
|
447
447
|
stream = agent.send("Solve: Which is greater, 9.11 or 9.9?", stream=True)
|
|
448
448
|
|
|
449
449
|
for chunk in stream:
|
|
@@ -517,59 +517,79 @@ Evidor delegates retry handling directly to the underlying model provider SDKs (
|
|
|
517
517
|
|
|
518
518
|
### Configurable Thinking & Reasoning Mode
|
|
519
519
|
|
|
520
|
-
Evidor provides
|
|
520
|
+
Evidor provides a centralized, provider-neutral `ThinkingConfig` for reasoning models across **OpenAI** (`gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna`), **Anthropic** (`claude-3-7-sonnet-20250219`), and **Gemini** (`gemini-3.8-flash`, `gemini-3.8-live-extended-thinking`, `gemini-3.1-pro-preview`).
|
|
521
|
+
|
|
522
|
+
Instead of navigating provider-specific configuration differences, you configure thinking through **two simple, unified modes**:
|
|
523
|
+
|
|
524
|
+
- **Mode 1: Shorthand Effort String** (e.g. `thinking="high"`, `thinking="medium"`, `thinking="low"`): Works universally across all providers. The effort level string is passed directly through to the underlying provider without client-side mapping or restrictions.
|
|
525
|
+
- **Mode 2: Centralized `ThinkingConfig` Object**: When you need granular control over reasoning effort, token budget, or summaries.
|
|
521
526
|
|
|
522
527
|
```python
|
|
523
528
|
from evidor import (
|
|
524
529
|
Agent,
|
|
525
530
|
AnthropicProvider,
|
|
526
|
-
AnthropicThinkingConfig,
|
|
527
531
|
GeminiProvider,
|
|
528
|
-
GeminiThinkingConfig,
|
|
529
532
|
OpenAIProvider,
|
|
530
|
-
|
|
533
|
+
ThinkingConfig,
|
|
531
534
|
)
|
|
532
535
|
|
|
533
|
-
#
|
|
534
|
-
#
|
|
535
|
-
|
|
536
|
-
#
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
#
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
#
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
536
|
+
# ----------------------------------------------------------------------
|
|
537
|
+
# Mode 1: Direct Effort String (universal across providers)
|
|
538
|
+
# ----------------------------------------------------------------------
|
|
539
|
+
# OpenAI (sets reasoning.effort):
|
|
540
|
+
p_openai = OpenAIProvider(model="gpt-6-astra", thinking="high")
|
|
541
|
+
|
|
542
|
+
# Anthropic (sets adaptive thinking with effort):
|
|
543
|
+
p_anthropic = AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high")
|
|
544
|
+
|
|
545
|
+
# Gemini (sets generation_config.thinking_level):
|
|
546
|
+
p_gemini = GeminiProvider(model="gemini-3.8-flash", thinking="high")
|
|
547
|
+
|
|
548
|
+
# ----------------------------------------------------------------------
|
|
549
|
+
# Mode 2: Centralized ThinkingConfig Object
|
|
550
|
+
# ----------------------------------------------------------------------
|
|
551
|
+
# 1. OpenAI: Configure effort and reasoning summary
|
|
552
|
+
p_openai_cfg = OpenAIProvider(
|
|
553
|
+
model="gpt-6-astra",
|
|
554
|
+
thinking=ThinkingConfig(effort="high", summary="auto"),
|
|
548
555
|
)
|
|
549
556
|
|
|
550
|
-
#
|
|
551
|
-
|
|
552
|
-
|
|
557
|
+
# 2. Anthropic: Configure extended budget tokens (budget_tokens >= 1024; auto-bumps max_tokens if needed)
|
|
558
|
+
p_anthropic_cfg = AnthropicProvider(
|
|
559
|
+
model="claude-3-7-sonnet-20250219",
|
|
560
|
+
thinking=ThinkingConfig(budget_tokens=4096),
|
|
561
|
+
)
|
|
553
562
|
|
|
554
|
-
#
|
|
555
|
-
|
|
563
|
+
# 3. Gemini: Configure thinking level and summaries (or thinking budget for earlier models)
|
|
564
|
+
p_gemini_cfg = GeminiProvider(
|
|
556
565
|
model="gemini-3.8-flash",
|
|
557
|
-
thinking=
|
|
566
|
+
thinking=ThinkingConfig(effort="medium", summary="auto"),
|
|
558
567
|
)
|
|
559
568
|
|
|
560
|
-
# Earlier models supporting numeric
|
|
561
|
-
|
|
569
|
+
# Earlier models supporting numeric thinking budget:
|
|
570
|
+
p_gemini_budget = GeminiProvider(
|
|
562
571
|
model="gemini-2.5-flash",
|
|
563
|
-
thinking=
|
|
572
|
+
thinking=ThinkingConfig(budget_tokens=2048),
|
|
564
573
|
)
|
|
565
574
|
```
|
|
566
575
|
|
|
576
|
+
#### Centralized `ThinkingConfig` Reference
|
|
577
|
+
|
|
578
|
+
| Field | Type | Description & Provider Mapping |
|
|
579
|
+
| :--- | :--- | :--- |
|
|
580
|
+
| `effort` | `str \| None` | Reasoning intensity. Passed through directly without client-side mapping or gating to ensure forward compatibility with new provider levels (e.g., `"none"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"`). Supported by OpenAI (`reasoning.effort`), Gemini (`thinking_level`), and Anthropic (`effort`). If an unexpected effort level is passed, the provider API handles it directly. |
|
|
581
|
+
| `budget_tokens` | `int \| None` | Maximum reasoning token budget where supported. Supported by Anthropic (`thinking={"type": "enabled", "budget_tokens": ...}`) and Gemini (`thinking_budget`). |
|
|
582
|
+
| `summary` | `str \| None` | Whether and how to expose reasoning summaries. Supported by OpenAI (`reasoning.summary`) and Gemini (`thinking_summaries`).<br>**Note on Gemini**: Gemini natively supports `"auto"` and `"none"`. When targeting Gemini, you should specify `"auto"` or `"none"`. As a fallback, Evidor automatically maps other non-`none` strings (e.g. `"concise"`, `"detailed"`) to `"auto"`. |
|
|
583
|
+
|
|
584
|
+
> [!NOTE]
|
|
585
|
+
> **No Boolean or Standalone Integer Shorthands**: Shorthands like `thinking=False` or `thinking=4096` are intentionally not supported to avoid ambiguity. To run without thinking, simply omit the parameter or pass `thinking=None`. To configure a token budget, use `ThinkingConfig(budget_tokens=4096)`.
|
|
586
|
+
|
|
567
587
|
#### Inspecting Thinking Steps & Reasoning Tokens
|
|
568
588
|
|
|
569
589
|
When thinking is enabled and the provider outputs thought tokens, the response provides both the visible text and the internal reasoning scratchpad:
|
|
570
590
|
|
|
571
591
|
```python
|
|
572
|
-
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking=
|
|
592
|
+
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high"))
|
|
573
593
|
response = agent.send("Solve this logic riddle step-by-step...")
|
|
574
594
|
|
|
575
595
|
# Final visible response
|
|
@@ -1186,7 +1206,7 @@ phoenix_sink = PhoenixSink(capture_content=True, capture_thinking=True)
|
|
|
1186
1206
|
langfuse_sink = LangfuseSink(capture_content=True, capture_thinking=True)
|
|
1187
1207
|
|
|
1188
1208
|
agent = Agent(
|
|
1189
|
-
AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking=
|
|
1209
|
+
AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high"),
|
|
1190
1210
|
telemetry=[console_sink, phoenix_sink],
|
|
1191
1211
|
)
|
|
1192
1212
|
response = agent.send("Analyze system security risks...")
|
|
@@ -356,7 +356,7 @@ For reasoning models (**OpenAI** `gpt-6-astra`, **Anthropic** `claude-3-7-sonnet
|
|
|
356
356
|
```python
|
|
357
357
|
from evidor import Agent, AnthropicProvider
|
|
358
358
|
|
|
359
|
-
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking=
|
|
359
|
+
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high"))
|
|
360
360
|
stream = agent.send("Solve: Which is greater, 9.11 or 9.9?", stream=True)
|
|
361
361
|
|
|
362
362
|
for chunk in stream:
|
|
@@ -430,59 +430,79 @@ Evidor delegates retry handling directly to the underlying model provider SDKs (
|
|
|
430
430
|
|
|
431
431
|
### Configurable Thinking & Reasoning Mode
|
|
432
432
|
|
|
433
|
-
Evidor provides
|
|
433
|
+
Evidor provides a centralized, provider-neutral `ThinkingConfig` for reasoning models across **OpenAI** (`gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna`), **Anthropic** (`claude-3-7-sonnet-20250219`), and **Gemini** (`gemini-3.8-flash`, `gemini-3.8-live-extended-thinking`, `gemini-3.1-pro-preview`).
|
|
434
|
+
|
|
435
|
+
Instead of navigating provider-specific configuration differences, you configure thinking through **two simple, unified modes**:
|
|
436
|
+
|
|
437
|
+
- **Mode 1: Shorthand Effort String** (e.g. `thinking="high"`, `thinking="medium"`, `thinking="low"`): Works universally across all providers. The effort level string is passed directly through to the underlying provider without client-side mapping or restrictions.
|
|
438
|
+
- **Mode 2: Centralized `ThinkingConfig` Object**: When you need granular control over reasoning effort, token budget, or summaries.
|
|
434
439
|
|
|
435
440
|
```python
|
|
436
441
|
from evidor import (
|
|
437
442
|
Agent,
|
|
438
443
|
AnthropicProvider,
|
|
439
|
-
AnthropicThinkingConfig,
|
|
440
444
|
GeminiProvider,
|
|
441
|
-
GeminiThinkingConfig,
|
|
442
445
|
OpenAIProvider,
|
|
443
|
-
|
|
446
|
+
ThinkingConfig,
|
|
444
447
|
)
|
|
445
448
|
|
|
446
|
-
#
|
|
447
|
-
#
|
|
448
|
-
|
|
449
|
-
#
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
#
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
#
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
449
|
+
# ----------------------------------------------------------------------
|
|
450
|
+
# Mode 1: Direct Effort String (universal across providers)
|
|
451
|
+
# ----------------------------------------------------------------------
|
|
452
|
+
# OpenAI (sets reasoning.effort):
|
|
453
|
+
p_openai = OpenAIProvider(model="gpt-6-astra", thinking="high")
|
|
454
|
+
|
|
455
|
+
# Anthropic (sets adaptive thinking with effort):
|
|
456
|
+
p_anthropic = AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high")
|
|
457
|
+
|
|
458
|
+
# Gemini (sets generation_config.thinking_level):
|
|
459
|
+
p_gemini = GeminiProvider(model="gemini-3.8-flash", thinking="high")
|
|
460
|
+
|
|
461
|
+
# ----------------------------------------------------------------------
|
|
462
|
+
# Mode 2: Centralized ThinkingConfig Object
|
|
463
|
+
# ----------------------------------------------------------------------
|
|
464
|
+
# 1. OpenAI: Configure effort and reasoning summary
|
|
465
|
+
p_openai_cfg = OpenAIProvider(
|
|
466
|
+
model="gpt-6-astra",
|
|
467
|
+
thinking=ThinkingConfig(effort="high", summary="auto"),
|
|
461
468
|
)
|
|
462
469
|
|
|
463
|
-
#
|
|
464
|
-
|
|
465
|
-
|
|
470
|
+
# 2. Anthropic: Configure extended budget tokens (budget_tokens >= 1024; auto-bumps max_tokens if needed)
|
|
471
|
+
p_anthropic_cfg = AnthropicProvider(
|
|
472
|
+
model="claude-3-7-sonnet-20250219",
|
|
473
|
+
thinking=ThinkingConfig(budget_tokens=4096),
|
|
474
|
+
)
|
|
466
475
|
|
|
467
|
-
#
|
|
468
|
-
|
|
476
|
+
# 3. Gemini: Configure thinking level and summaries (or thinking budget for earlier models)
|
|
477
|
+
p_gemini_cfg = GeminiProvider(
|
|
469
478
|
model="gemini-3.8-flash",
|
|
470
|
-
thinking=
|
|
479
|
+
thinking=ThinkingConfig(effort="medium", summary="auto"),
|
|
471
480
|
)
|
|
472
481
|
|
|
473
|
-
# Earlier models supporting numeric
|
|
474
|
-
|
|
482
|
+
# Earlier models supporting numeric thinking budget:
|
|
483
|
+
p_gemini_budget = GeminiProvider(
|
|
475
484
|
model="gemini-2.5-flash",
|
|
476
|
-
thinking=
|
|
485
|
+
thinking=ThinkingConfig(budget_tokens=2048),
|
|
477
486
|
)
|
|
478
487
|
```
|
|
479
488
|
|
|
489
|
+
#### Centralized `ThinkingConfig` Reference
|
|
490
|
+
|
|
491
|
+
| Field | Type | Description & Provider Mapping |
|
|
492
|
+
| :--- | :--- | :--- |
|
|
493
|
+
| `effort` | `str \| None` | Reasoning intensity. Passed through directly without client-side mapping or gating to ensure forward compatibility with new provider levels (e.g., `"none"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"`). Supported by OpenAI (`reasoning.effort`), Gemini (`thinking_level`), and Anthropic (`effort`). If an unexpected effort level is passed, the provider API handles it directly. |
|
|
494
|
+
| `budget_tokens` | `int \| None` | Maximum reasoning token budget where supported. Supported by Anthropic (`thinking={"type": "enabled", "budget_tokens": ...}`) and Gemini (`thinking_budget`). |
|
|
495
|
+
| `summary` | `str \| None` | Whether and how to expose reasoning summaries. Supported by OpenAI (`reasoning.summary`) and Gemini (`thinking_summaries`).<br>**Note on Gemini**: Gemini natively supports `"auto"` and `"none"`. When targeting Gemini, you should specify `"auto"` or `"none"`. As a fallback, Evidor automatically maps other non-`none` strings (e.g. `"concise"`, `"detailed"`) to `"auto"`. |
|
|
496
|
+
|
|
497
|
+
> [!NOTE]
|
|
498
|
+
> **No Boolean or Standalone Integer Shorthands**: Shorthands like `thinking=False` or `thinking=4096` are intentionally not supported to avoid ambiguity. To run without thinking, simply omit the parameter or pass `thinking=None`. To configure a token budget, use `ThinkingConfig(budget_tokens=4096)`.
|
|
499
|
+
|
|
480
500
|
#### Inspecting Thinking Steps & Reasoning Tokens
|
|
481
501
|
|
|
482
502
|
When thinking is enabled and the provider outputs thought tokens, the response provides both the visible text and the internal reasoning scratchpad:
|
|
483
503
|
|
|
484
504
|
```python
|
|
485
|
-
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking=
|
|
505
|
+
agent = Agent(AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high"))
|
|
486
506
|
response = agent.send("Solve this logic riddle step-by-step...")
|
|
487
507
|
|
|
488
508
|
# Final visible response
|
|
@@ -1099,7 +1119,7 @@ phoenix_sink = PhoenixSink(capture_content=True, capture_thinking=True)
|
|
|
1099
1119
|
langfuse_sink = LangfuseSink(capture_content=True, capture_thinking=True)
|
|
1100
1120
|
|
|
1101
1121
|
agent = Agent(
|
|
1102
|
-
AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking=
|
|
1122
|
+
AnthropicProvider(model="claude-3-7-sonnet-20250219", thinking="high"),
|
|
1103
1123
|
telemetry=[console_sink, phoenix_sink],
|
|
1104
1124
|
)
|
|
1105
1125
|
response = agent.send("Analyze system security risks...")
|
|
@@ -13,18 +13,16 @@ from .mcp import (
|
|
|
13
13
|
)
|
|
14
14
|
from .models import (
|
|
15
15
|
AnyThinkingConfig,
|
|
16
|
-
AnthropicThinkingConfig,
|
|
17
16
|
AsyncStreamResponse,
|
|
18
|
-
GeminiThinkingConfig,
|
|
19
17
|
GenerationRequest,
|
|
20
18
|
GenerationResponse,
|
|
21
19
|
Message,
|
|
22
|
-
OpenAIThinkingConfig,
|
|
23
20
|
SearchResult,
|
|
24
21
|
StreamChunk,
|
|
25
22
|
StreamResponse,
|
|
26
23
|
ThinkingConfig,
|
|
27
24
|
ThinkingEffort,
|
|
25
|
+
ThinkingSummary,
|
|
28
26
|
ToolCall,
|
|
29
27
|
)
|
|
30
28
|
|
|
@@ -77,7 +75,6 @@ from .tools import (
|
|
|
77
75
|
__all__ = [
|
|
78
76
|
"Agent",
|
|
79
77
|
"AnthropicProvider",
|
|
80
|
-
"AnthropicThinkingConfig",
|
|
81
78
|
"AnyThinkingConfig",
|
|
82
79
|
"AsyncStreamResponse",
|
|
83
80
|
"BraveSearchProvider",
|
|
@@ -91,7 +88,6 @@ __all__ = [
|
|
|
91
88
|
"ExaSearchProvider",
|
|
92
89
|
"fetch_webpage_content",
|
|
93
90
|
"GeminiProvider",
|
|
94
|
-
"GeminiThinkingConfig",
|
|
95
91
|
"GenerationRequest",
|
|
96
92
|
"GenerationResponse",
|
|
97
93
|
"get_current_time",
|
|
@@ -105,7 +101,6 @@ __all__ = [
|
|
|
105
101
|
"Message",
|
|
106
102
|
"ModelProvider",
|
|
107
103
|
"OpenAIProvider",
|
|
108
|
-
"OpenAIThinkingConfig",
|
|
109
104
|
"OpenTelemetrySink",
|
|
110
105
|
"PhoenixSink",
|
|
111
106
|
"PrometheusSink",
|
|
@@ -121,6 +116,7 @@ __all__ = [
|
|
|
121
116
|
"TelemetrySink",
|
|
122
117
|
"ThinkingConfig",
|
|
123
118
|
"ThinkingEffort",
|
|
119
|
+
"ThinkingSummary",
|
|
124
120
|
"Tool",
|
|
125
121
|
"ToolCall",
|
|
126
122
|
"TraceContext",
|
|
@@ -27,7 +27,7 @@ class AnthropicPayloadBuilder:
|
|
|
27
27
|
self.compact_threshold = compact_threshold
|
|
28
28
|
self.compact_instructions = compact_instructions
|
|
29
29
|
|
|
30
|
-
def build(self, request: GenerationRequest) -> dict[str, Any]:
|
|
30
|
+
def build(self, request: GenerationRequest, thinking: Any = None) -> dict[str, Any]:
|
|
31
31
|
"""Construct the full request dictionary for Anthropic Messages API."""
|
|
32
32
|
system_prompt = extract_system_prompt(request)
|
|
33
33
|
formatted_messages = self.format_messages(request.messages)
|
|
@@ -48,22 +48,32 @@ class AnthropicPayloadBuilder:
|
|
|
48
48
|
payload["betas"] = ["compact-2026-01-12"]
|
|
49
49
|
payload["context_management"] = {"edits": [edit]}
|
|
50
50
|
|
|
51
|
-
if
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
|
|
51
|
+
active_thinking = thinking if thinking is not None else self.thinking
|
|
52
|
+
if active_thinking and isinstance(active_thinking, str):
|
|
53
|
+
payload["thinking"] = {"type": "adaptive"}
|
|
54
|
+
payload["output_config"] = {"effort": active_thinking}
|
|
55
|
+
elif active_thinking and not isinstance(active_thinking, (bool, int)):
|
|
56
|
+
budget = (
|
|
57
|
+
active_thinking.get("budget_tokens")
|
|
58
|
+
if isinstance(active_thinking, dict)
|
|
59
|
+
else getattr(active_thinking, "budget_tokens", None)
|
|
60
|
+
)
|
|
61
|
+
effort = (
|
|
62
|
+
active_thinking.get("effort")
|
|
63
|
+
if isinstance(active_thinking, dict)
|
|
64
|
+
else getattr(active_thinking, "effort", None)
|
|
65
|
+
)
|
|
66
|
+
if budget is not None and not isinstance(budget, bool) and budget > 0:
|
|
67
|
+
actual_budget = max(budget, 1024)
|
|
60
68
|
payload["thinking"] = {
|
|
61
69
|
"type": "enabled",
|
|
62
|
-
"budget_tokens":
|
|
70
|
+
"budget_tokens": actual_budget,
|
|
63
71
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
if payload["max_tokens"] <= actual_budget:
|
|
73
|
+
payload["max_tokens"] = actual_budget + 1024
|
|
74
|
+
elif isinstance(effort, str):
|
|
75
|
+
payload["thinking"] = {"type": "adaptive"}
|
|
76
|
+
payload["output_config"] = {"effort": effort}
|
|
67
77
|
|
|
68
78
|
if self.enable_prompt_caching:
|
|
69
79
|
payload["cache_control"] = {"type": "ephemeral"}
|
|
@@ -47,19 +47,7 @@ class AnthropicProvider(BaseProvider):
|
|
|
47
47
|
compact_instructions: str | None = None,
|
|
48
48
|
base_url: str | None = None,
|
|
49
49
|
) -> None:
|
|
50
|
-
|
|
51
|
-
from evidor.models import AnthropicThinkingConfig
|
|
52
|
-
|
|
53
|
-
thinking_cfg: AnthropicThinkingConfig | None = (
|
|
54
|
-
thinking
|
|
55
|
-
if isinstance(thinking, AnthropicThinkingConfig)
|
|
56
|
-
else AnthropicThinkingConfig.from_value(thinking)
|
|
57
|
-
)
|
|
58
|
-
thinking_cfg.validate()
|
|
59
|
-
else:
|
|
60
|
-
thinking_cfg = None
|
|
61
|
-
|
|
62
|
-
super().__init__(model=model, api_key=api_key, thinking=thinking_cfg)
|
|
50
|
+
super().__init__(model=model, api_key=api_key, thinking=thinking)
|
|
63
51
|
self._enable_prompt_caching = enable_prompt_caching
|
|
64
52
|
self._compact_threshold = compact_threshold
|
|
65
53
|
self._compact_instructions = compact_instructions
|
|
@@ -69,8 +57,13 @@ class AnthropicProvider(BaseProvider):
|
|
|
69
57
|
self._async_clients: dict[asyncio.AbstractEventLoop, Any] = {}
|
|
70
58
|
|
|
71
59
|
if max_tokens is None:
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
budget = (
|
|
61
|
+
self._thinking.get("budget_tokens")
|
|
62
|
+
if isinstance(self._thinking, dict)
|
|
63
|
+
else getattr(self._thinking, "budget_tokens", None)
|
|
64
|
+
)
|
|
65
|
+
if budget is not None and not isinstance(budget, bool) and budget > 0:
|
|
66
|
+
max_tokens = budget + 2048
|
|
74
67
|
else:
|
|
75
68
|
max_tokens = 1024
|
|
76
69
|
self._max_tokens = max_tokens
|
|
@@ -175,31 +175,34 @@ class GeminiPayloadBuilder:
|
|
|
175
175
|
active_thinking = thinking if thinking is not None else self.thinking
|
|
176
176
|
if active_thinking and isinstance(active_thinking, str):
|
|
177
177
|
payload["generation_config"] = {"thinking_level": active_thinking}
|
|
178
|
-
elif active_thinking and not isinstance(active_thinking, bool):
|
|
178
|
+
elif active_thinking and not isinstance(active_thinking, (bool, int)):
|
|
179
179
|
gen_config: dict[str, Any] = {}
|
|
180
|
-
|
|
181
|
-
active_thinking.get("thinking_level")
|
|
180
|
+
effort = (
|
|
181
|
+
active_thinking.get("effort") or active_thinking.get("thinking_level")
|
|
182
182
|
if isinstance(active_thinking, dict)
|
|
183
|
-
else getattr(active_thinking, "thinking_level", None)
|
|
183
|
+
else getattr(active_thinking, "effort", None) or getattr(active_thinking, "thinking_level", None)
|
|
184
184
|
)
|
|
185
|
-
if isinstance(
|
|
186
|
-
gen_config["thinking_level"] =
|
|
185
|
+
if isinstance(effort, str):
|
|
186
|
+
gen_config["thinking_level"] = effort
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
active_thinking.get("thinking_summaries")
|
|
188
|
+
summary = (
|
|
189
|
+
active_thinking.get("summary") or active_thinking.get("thinking_summaries")
|
|
190
190
|
if isinstance(active_thinking, dict)
|
|
191
|
-
else getattr(active_thinking, "thinking_summaries", None)
|
|
191
|
+
else getattr(active_thinking, "summary", None) or getattr(active_thinking, "thinking_summaries", None)
|
|
192
192
|
)
|
|
193
|
-
if isinstance(
|
|
194
|
-
|
|
193
|
+
if isinstance(summary, str):
|
|
194
|
+
if summary.lower() == "none":
|
|
195
|
+
gen_config["thinking_summaries"] = "none"
|
|
196
|
+
else:
|
|
197
|
+
gen_config["thinking_summaries"] = "auto"
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
active_thinking.get("thinking_budget")
|
|
199
|
+
budget = (
|
|
200
|
+
active_thinking.get("budget_tokens") or active_thinking.get("thinking_budget")
|
|
198
201
|
if isinstance(active_thinking, dict)
|
|
199
|
-
else getattr(active_thinking, "thinking_budget", None)
|
|
202
|
+
else getattr(active_thinking, "budget_tokens", None) or getattr(active_thinking, "thinking_budget", None)
|
|
200
203
|
)
|
|
201
|
-
if
|
|
202
|
-
gen_config["thinking_budget"] =
|
|
204
|
+
if budget is not None and not isinstance(budget, bool) and budget > 0:
|
|
205
|
+
gen_config["thinking_budget"] = budget
|
|
203
206
|
|
|
204
207
|
if gen_config:
|
|
205
208
|
payload["generation_config"] = gen_config
|
|
@@ -21,7 +21,7 @@ class OpenAIPayloadBuilder:
|
|
|
21
21
|
self.thinking = thinking
|
|
22
22
|
self.compact_threshold = compact_threshold
|
|
23
23
|
|
|
24
|
-
def build(self, request: GenerationRequest) -> dict[str, Any]:
|
|
24
|
+
def build(self, request: GenerationRequest, thinking: Any = None) -> dict[str, Any]:
|
|
25
25
|
"""Construct the full request dictionary for OpenAI Responses API."""
|
|
26
26
|
payload: dict[str, Any] = {
|
|
27
27
|
"model": self.model,
|
|
@@ -42,16 +42,27 @@ class OpenAIPayloadBuilder:
|
|
|
42
42
|
{"type": "compaction", "compact_threshold": self.compact_threshold}
|
|
43
43
|
]
|
|
44
44
|
|
|
45
|
-
if
|
|
45
|
+
active_thinking = thinking if thinking is not None else self.thinking
|
|
46
|
+
if active_thinking and isinstance(active_thinking, str):
|
|
47
|
+
payload["reasoning"] = {"effort": active_thinking}
|
|
48
|
+
elif active_thinking and not isinstance(active_thinking, (bool, int)):
|
|
46
49
|
reasoning_config: dict[str, Any] = {}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
effort = (
|
|
51
|
+
active_thinking.get("effort")
|
|
52
|
+
if isinstance(active_thinking, dict)
|
|
53
|
+
else getattr(active_thinking, "effort", None)
|
|
54
|
+
)
|
|
55
|
+
if isinstance(effort, str):
|
|
56
|
+
reasoning_config["effort"] = effort
|
|
57
|
+
|
|
58
|
+
summary_val = (
|
|
59
|
+
active_thinking.get("summary")
|
|
60
|
+
if isinstance(active_thinking, dict)
|
|
61
|
+
else getattr(active_thinking, "summary", None)
|
|
62
|
+
)
|
|
63
|
+
if isinstance(summary_val, str) and summary_val.lower() != "none":
|
|
64
|
+
reasoning_config["summary"] = summary_val
|
|
65
|
+
|
|
55
66
|
if reasoning_config:
|
|
56
67
|
payload["reasoning"] = reasoning_config
|
|
57
68
|
|
|
@@ -33,19 +33,7 @@ class OpenAIProvider(BaseProvider):
|
|
|
33
33
|
compact_threshold: int | None = None,
|
|
34
34
|
base_url: str | None = None,
|
|
35
35
|
) -> None:
|
|
36
|
-
|
|
37
|
-
from evidor.models import OpenAIThinkingConfig
|
|
38
|
-
|
|
39
|
-
thinking_cfg: OpenAIThinkingConfig | None = (
|
|
40
|
-
thinking
|
|
41
|
-
if isinstance(thinking, OpenAIThinkingConfig)
|
|
42
|
-
else OpenAIThinkingConfig.from_value(thinking)
|
|
43
|
-
)
|
|
44
|
-
thinking_cfg.validate()
|
|
45
|
-
else:
|
|
46
|
-
thinking_cfg = None
|
|
47
|
-
|
|
48
|
-
super().__init__(model=model, api_key=api_key, thinking=thinking_cfg)
|
|
36
|
+
super().__init__(model=model, api_key=api_key, thinking=thinking)
|
|
49
37
|
self._compact_threshold = compact_threshold
|
|
50
38
|
self._base_url = base_url
|
|
51
39
|
self._sync_client: Any = None
|
|
@@ -14,20 +14,16 @@ from .streaming import (
|
|
|
14
14
|
StreamResponse,
|
|
15
15
|
)
|
|
16
16
|
from .thinking import (
|
|
17
|
-
AnthropicThinkingConfig,
|
|
18
17
|
AnyThinkingConfig,
|
|
19
|
-
GeminiThinkingConfig,
|
|
20
|
-
OpenAIThinkingConfig,
|
|
21
18
|
ThinkingConfig,
|
|
22
19
|
ThinkingEffort,
|
|
20
|
+
ThinkingSummary,
|
|
23
21
|
)
|
|
24
22
|
|
|
25
23
|
__all__ = [
|
|
26
24
|
"ThinkingConfig",
|
|
27
25
|
"ThinkingEffort",
|
|
28
|
-
"
|
|
29
|
-
"AnthropicThinkingConfig",
|
|
30
|
-
"GeminiThinkingConfig",
|
|
26
|
+
"ThinkingSummary",
|
|
31
27
|
"AnyThinkingConfig",
|
|
32
28
|
"SearchResult",
|
|
33
29
|
"ToolCall",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Thinking and reasoning configuration for models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
ThinkingEffort = str
|
|
9
|
+
ThinkingSummary = str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class ThinkingConfig:
|
|
14
|
+
"""Centralized, provider-neutral reasoning and thinking configuration.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
effort: Desired thinking depth or reasoning level (e.g. 'minimal', 'low', 'medium', 'high', 'xhigh', 'max').
|
|
18
|
+
Passed directly to the provider without client-side mapping or restriction.
|
|
19
|
+
budget_tokens: Token budget for reasoning where supported (e.g. Anthropic extended thinking or earlier Gemini models).
|
|
20
|
+
summary: Reasoning summary exposure (e.g. 'auto', 'concise', 'detailed', 'none').
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
effort: ThinkingEffort | None = None
|
|
24
|
+
budget_tokens: int | None = None
|
|
25
|
+
summary: ThinkingSummary | None = None
|
|
26
|
+
|
|
27
|
+
def validate(self) -> None:
|
|
28
|
+
"""Provider-neutral constraint checks."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_value(cls, value: Any) -> ThinkingConfig:
|
|
33
|
+
"""Coerce a value to ThinkingConfig."""
|
|
34
|
+
if isinstance(value, ThinkingConfig):
|
|
35
|
+
return value
|
|
36
|
+
if isinstance(value, str):
|
|
37
|
+
return cls(effort=value)
|
|
38
|
+
if isinstance(value, dict):
|
|
39
|
+
return cls(
|
|
40
|
+
effort=value.get("effort"),
|
|
41
|
+
budget_tokens=value.get("budget_tokens"),
|
|
42
|
+
summary=value.get("summary"),
|
|
43
|
+
)
|
|
44
|
+
raise TypeError(
|
|
45
|
+
f"Cannot coerce {type(value).__name__} to ThinkingConfig. "
|
|
46
|
+
"Thinking must be configured either as an effort string (e.g. thinking='high') "
|
|
47
|
+
"or via ThinkingConfig(effort=..., budget_tokens=..., summary=...)."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
AnyThinkingConfig = ThinkingConfig
|