vet

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 6daf60fe2f7b0613a81e577e9591a7267c7729b6
parent ccea1dfd0be889ad7c00bf5c7b1b70a97585344f
Author: Andrew Laack <andrew@laack.co>
Date:   Thu, 16 Apr 2026 11:52:51 -0700

Add Claude Opus 4.7 support, make it the default (#194)

* Add Claude Opus 4.7 support and make it the default model

- Add claude-opus-4-7 and claude-opus-4-7-long to AnthropicModelName enum
- Add ModelInfo entries with pricing from Anthropic docs ($5/$25 per MTok)
- Update DEFAULT_MODEL_ID from claude-opus-4-6 to claude-opus-4-7
- Update VetConfig defaults to use CLAUDE_4_7_OPUS
- Update long-context mapping for both streaming and non-streaming paths
- Update CLI help text, skill docs, and models.json opus alias
- Opus 4.6 remains fully defined and usable via --model claude-opus-4-6

* Keep 'opus' alias on 4.6 for forwards compatibility, add 'opus-4.7' alias

The 'opus' shorthand in .vet/models.json is used by configs.toml
CI profiles. Keep it stable on 4.6 to avoid breaking existing
workflows. Add a separate 'opus-4.7' alias for explicit use.

* Fix black formatting in changed files

* Rename 4.7 to be opus

* Skip temperature parameter for Opus 4.7 (not supported)

Opus 4.7 returns 400 if temperature, top_p, or top_k are set.
Pass NOT_GIVEN for temperature on Opus 4.7 and 4.7-long models.

* Fix models.json: correct max_output_tokens, keep opus on 4.6, disable temperature for 4.7

- opus alias stays on claude-opus-4-6 (forwards compatibility)
- opus-4.7 alias added with supports_temperature: false
- Fixed max_output_tokens: sonnet/haiku 64k, opus 128k (were all 16k)
- Removed stale opus-4.6 alias (opus already points to 4.6)

* Point 'opus' alias to 4.7, add 'opus-4.6' for explicit access

opus is the latest (4.7, no temperature). Use opus-4.6 to
explicitly select the previous version.
Diffstat:
M.vet/models.json | 14++++++++++----
Mskills/vet/SKILL.md | 2+-
Muv.lock | 2+-
Mvet/cli/main.py | 2+-
Mvet/cli/models.py | 2+-
Mvet/imbue_core/agents/llm_apis/anthropic_api.py | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mvet/imbue_tools/types/vet_config.py | 4++--
7 files changed, 74 insertions(+), 39 deletions(-)

diff --git a/.vet/models.json b/.vet/models.json @@ -41,20 +41,26 @@ "sonnet": { "model_id": "claude-sonnet-4-6", "context_window": 200000, - "max_output_tokens": 16384, + "max_output_tokens": 64000, "supports_temperature": true }, "haiku": { "model_id": "claude-haiku-4-5", "context_window": 200000, - "max_output_tokens": 16384, + "max_output_tokens": 64000, "supports_temperature": true }, - "opus": { + "opus-4.6": { "model_id": "claude-opus-4-6", "context_window": 200000, - "max_output_tokens": 16384, + "max_output_tokens": 128000, "supports_temperature": true + }, + "opus": { + "model_id": "claude-opus-4-7", + "context_window": 200000, + "max_output_tokens": 128000, + "supports_temperature": false } } }, diff --git a/skills/vet/SKILL.md b/skills/vet/SKILL.md @@ -99,7 +99,7 @@ Vet analyzes the full git diff from the base commit. This may include changes fr ## Common Options - `--base-commit REF`: Git ref for diff base (default: HEAD) -- `--model MODEL`: LLM to use (default: claude-opus-4-6) +- `--model MODEL`: LLM to use (default: claude-opus-4-7) - `--list-models`: list all models that are supported by vet - Run `vet --help` and look at the vet repo's readme for details about defining custom OpenAI-compatible models. - `--update-models`: fetch the latest community model definitions from the remote registry and cache them locally. See "Updating the Model Registry" below for when to run this. diff --git a/uv.lock b/uv.lock @@ -1494,7 +1494,7 @@ wheels = [ [[package]] name = "verify-everything" -version = "0.2.7" +version = "0.2.9" source = { editable = "." } dependencies = [ { name = "anthropic" }, diff --git a/vet/cli/main.py b/vet/cli/main.py @@ -152,7 +152,7 @@ def create_parser() -> argparse.ArgumentParser: default=CLI_DEFAULTS.model, metavar="MODEL", # Hardcoded to avoid importing cli.models at module level (~1s of SDK imports). - help="LLM to use for analysis (default: claude-opus-4-6).", + help="LLM to use for analysis (default: claude-opus-4-7).", ) model_group.add_argument( "--list-models", diff --git a/vet/cli/models.py b/vet/cli/models.py @@ -12,7 +12,7 @@ from vet.imbue_core.agents.llm_apis.common import get_all_model_names from vet.imbue_core.agents.llm_apis.gemini_api import GeminiModelName from vet.imbue_core.agents.llm_apis.openai_api import OpenAIModelName -DEFAULT_MODEL_ID = AnthropicModelName.CLAUDE_4_6_OPUS.value +DEFAULT_MODEL_ID = AnthropicModelName.CLAUDE_4_7_OPUS.value class MissingProviderAPIKeyError(Exception): diff --git a/vet/imbue_core/agents/llm_apis/anthropic_api.py b/vet/imbue_core/agents/llm_apis/anthropic_api.py @@ -59,6 +59,7 @@ class AnthropicModelName(enum.StrEnum): CLAUDE_4_1_OPUS = "claude-opus-4-1" CLAUDE_4_5_OPUS = "claude-opus-4-5" CLAUDE_4_6_OPUS = "claude-opus-4-6" + CLAUDE_4_7_OPUS = "claude-opus-4-7" CLAUDE_4_SONNET = "claude-sonnet-4-0" CLAUDE_4_5_SONNET = "claude-sonnet-4-5" CLAUDE_4_6_SONNET = "claude-sonnet-4-6" @@ -69,6 +70,7 @@ class AnthropicModelName(enum.StrEnum): CLAUDE_4_SONNET_LONG = "claude-sonnet-4-0-long" CLAUDE_4_5_SONNET_LONG = "claude-sonnet-4-5-long" CLAUDE_4_6_OPUS_LONG = "claude-opus-4-6-long" + CLAUDE_4_7_OPUS_LONG = "claude-opus-4-7-long" # Basic info is available at https://docs.anthropic.com/claude/reference/models @@ -138,6 +140,21 @@ ANTHROPIC_MODEL_INFO_BY_NAME: FrozenMapping[AnthropicModelName, ModelInfo] = Fro cost_per_cache_read_token=0.50 / 1_000_000, ), ), + AnthropicModelName.CLAUDE_4_7_OPUS: ModelInfo( + model_name=AnthropicModelName.CLAUDE_4_7_OPUS, + cost_per_input_token=5.00 / 1_000_000, + cost_per_output_token=25.00 / 1_000_000, + max_input_tokens=200_000, + max_output_tokens=128_000, + rate_limit_req=4000 / 60, + rate_limit_tok=2_000_000 / 60, + rate_limit_output_tok=400_000 / 60, + provider_specific_info=AnthropicModelInfo( + cost_per_5m_cache_write_token=6.25 / 1_000_000, + cost_per_1h_cache_write_token=10 / 1_000_000, + cost_per_cache_read_token=0.50 / 1_000_000, + ), + ), AnthropicModelName.CLAUDE_4_SONNET: ModelInfo( model_name=AnthropicModelName.CLAUDE_4_SONNET, cost_per_input_token=3.00 / 1_000_000, @@ -237,10 +254,30 @@ ANTHROPIC_MODEL_INFO_BY_NAME: FrozenMapping[AnthropicModelName, ModelInfo] = Fro rate_limit_tok=1_000_000 / 60, rate_limit_output_tok=200_000 / 60, ), + AnthropicModelName.CLAUDE_4_7_OPUS_LONG: ModelInfo( + model_name=AnthropicModelName.CLAUDE_4_7_OPUS_LONG, + # Opus 4.7 has a native 1M context window. Pricing tiers match 4.6 long-context. + cost_per_input_token=9.00 / 1_000_000, + cost_per_output_token=37.50 / 1_000_000, + max_input_tokens=1_000_000, + max_output_tokens=128_000, + rate_limit_req=None, # Currently no limit set in our dashboard + rate_limit_tok=1_000_000 / 60, + rate_limit_output_tok=200_000 / 60, + ), } ) +# Opus 4.7+ does not support temperature, top_p, or top_k parameters. +# Passing any non-default value returns a 400 error. +_MODELS_WITHOUT_TEMPERATURE: frozenset[AnthropicModelName] = frozenset( + { + AnthropicModelName.CLAUDE_4_7_OPUS, + AnthropicModelName.CLAUDE_4_7_OPUS_LONG, + } +) + _ROLE_TO_ANTHROPIC_ROLE: Final[FrozenMapping[str, str]] = FrozenDict( { "HUMAN": "user", @@ -462,26 +499,22 @@ class AnthropicAPI(LanguageModelAPI): params = chill(param_with_max_tokens_evolver) assert params.max_tokens is not None, "max_tokens must be provided for Anthropic API" - if self.model_name in ( - AnthropicModelName.CLAUDE_4_5_SONNET_LONG, - AnthropicModelName.CLAUDE_4_SONNET_LONG, - AnthropicModelName.CLAUDE_4_6_OPUS_LONG, - ): + _LONG_TO_STANDARD = { + AnthropicModelName.CLAUDE_4_5_SONNET_LONG: AnthropicModelName.CLAUDE_4_5_SONNET, + AnthropicModelName.CLAUDE_4_SONNET_LONG: AnthropicModelName.CLAUDE_4_SONNET, + AnthropicModelName.CLAUDE_4_6_OPUS_LONG: AnthropicModelName.CLAUDE_4_6_OPUS, + AnthropicModelName.CLAUDE_4_7_OPUS_LONG: AnthropicModelName.CLAUDE_4_7_OPUS, + } + + if self.model_name in _LONG_TO_STANDARD: # FIXME: Fix this once this is no longer beta or as this becomes required for more models # Map the name back to the actual model name for the API call - if self.model_name == AnthropicModelName.CLAUDE_4_5_SONNET_LONG: - model_name = AnthropicModelName.CLAUDE_4_5_SONNET - elif self.model_name == AnthropicModelName.CLAUDE_4_SONNET_LONG: - model_name = AnthropicModelName.CLAUDE_4_SONNET - elif self.model_name == AnthropicModelName.CLAUDE_4_6_OPUS_LONG: - model_name = AnthropicModelName.CLAUDE_4_6_OPUS - else: - assert False, "unreachable" + model_name = _LONG_TO_STANDARD[self.model_name] api_result = await client.beta.messages.create( messages=non_system_messages, stop_sequences=([params.stop] if params.stop is not None else NOT_GIVEN), model=model_name, - temperature=params.temperature, + temperature=NOT_GIVEN if self.model_name in _MODELS_WITHOUT_TEMPERATURE else params.temperature, system=prepend_claude_code_system_prompt(system_messages), max_tokens=params.max_tokens, betas=["context-1m-2025-08-07"], @@ -495,7 +528,7 @@ class AnthropicAPI(LanguageModelAPI): messages=non_system_messages, stop_sequences=([params.stop] if params.stop is not None else NOT_GIVEN), model=self.model_name, - temperature=params.temperature, + temperature=NOT_GIVEN if self.model_name in _MODELS_WITHOUT_TEMPERATURE else params.temperature, system=prepend_claude_code_system_prompt(system_messages), max_tokens=params.max_tokens, ) @@ -547,21 +580,17 @@ class AnthropicAPI(LanguageModelAPI): max_tokens = params.max_tokens if params.max_tokens is not None else self.model_info.max_output_tokens assert max_tokens is not None, "max_tokens must be provided for Anthropic API" - if self.model_name in ( - AnthropicModelName.CLAUDE_4_5_SONNET_LONG, - AnthropicModelName.CLAUDE_4_SONNET_LONG, - AnthropicModelName.CLAUDE_4_6_OPUS_LONG, - ): + _LONG_TO_STANDARD_STREAM = { + AnthropicModelName.CLAUDE_4_5_SONNET_LONG: AnthropicModelName.CLAUDE_4_5_SONNET, + AnthropicModelName.CLAUDE_4_SONNET_LONG: AnthropicModelName.CLAUDE_4_SONNET, + AnthropicModelName.CLAUDE_4_6_OPUS_LONG: AnthropicModelName.CLAUDE_4_6_OPUS, + AnthropicModelName.CLAUDE_4_7_OPUS_LONG: AnthropicModelName.CLAUDE_4_7_OPUS, + } + + if self.model_name in _LONG_TO_STANDARD_STREAM: # FIXME: Fix this once this is no longer beta or as this becomes required for more models # Map the name back to the actual model name for the API call - if self.model_name == AnthropicModelName.CLAUDE_4_5_SONNET_LONG: - model_name = AnthropicModelName.CLAUDE_4_5_SONNET - elif self.model_name == AnthropicModelName.CLAUDE_4_SONNET_LONG: - model_name = AnthropicModelName.CLAUDE_4_SONNET - elif self.model_name == AnthropicModelName.CLAUDE_4_6_OPUS_LONG: - model_name = AnthropicModelName.CLAUDE_4_6_OPUS - else: - assert False, "unreachable" + model_name = _LONG_TO_STANDARD_STREAM[self.model_name] stream_fn = lambda **kwargs: client.beta.messages.stream(**kwargs, betas=["context-1m-2025-08-07"]) cache_info_maker = lambda api_result: AnthropicCachingInfo( written_5m=api_result.usage.cache_creation.ephemeral_5m_input_tokens, @@ -580,7 +609,7 @@ class AnthropicAPI(LanguageModelAPI): model=model_name, stop_sequences=([params.stop] if params.stop is not None else NOT_GIVEN), system=system_messages or NOT_GIVEN, - temperature=params.temperature, + temperature=NOT_GIVEN if self.model_name in _MODELS_WITHOUT_TEMPERATURE else params.temperature, ) as stream: async for text_delta in stream.text_stream: yield LanguageModelStreamDeltaEvent(delta=text_delta) diff --git a/vet/imbue_tools/types/vet_config.py b/vet/imbue_tools/types/vet_config.py @@ -31,7 +31,7 @@ class VetConfig(SerializableModel): # Todo: Different models for different issue identifiers language_model_generation_config: LanguageModelGenerationConfig = LanguageModelGenerationConfig( - model_name=AnthropicModelName.CLAUDE_4_6_OPUS + model_name=AnthropicModelName.CLAUDE_4_7_OPUS ) max_identifier_spend_dollars: float | None = None max_output_tokens: int = 20000 @@ -75,7 +75,7 @@ class VetConfig(SerializableModel): cache_full_prompt: bool = False, ) -> "VetConfig": if not language_model_name: - language_model_name = AnthropicModelName.CLAUDE_4_6_OPUS + language_model_name = AnthropicModelName.CLAUDE_4_7_OPUS language_model_generation_config = LanguageModelGenerationConfig( model_name=language_model_name, cache_path=language_model_cache_path,