|
1 | 1 | import logging |
| 2 | +import re |
2 | 3 |
|
3 | 4 | import boto3 |
4 | 5 | from botocore.config import Config as BotoConfig |
|
8 | 9 |
|
9 | 10 | _CIRCUIT_BREAKER_THRESHOLD = 3 |
10 | 11 |
|
| 12 | +# Models that reject `temperature` (and `top_p`, `top_k`) in inferenceConfig. |
| 13 | +# Opus 4.7 was the first; pattern is anchored at a non-digit boundary so we |
| 14 | +# match `claude-opus-4-7` and `claude-opus-4-7-mini-...` but not a hypothetical |
| 15 | +# `opus-4-70`. Add new families here as they drop sampling params. |
| 16 | +_NO_SAMPLING_PARAMS_PATTERN = re.compile(r"opus-4-7(?!\d)") |
| 17 | + |
| 18 | + |
| 19 | +def _build_inference_config(max_tokens, temperature, model_id): |
| 20 | + """Compose inferenceConfig for converse(). Drops temperature for models |
| 21 | + that reject it; leaves the field on older Claude models so we keep the |
| 22 | + existing 0.3 sampling behavior for Reporter/Haiku/legacy.""" |
| 23 | + cfg = {"maxTokens": max_tokens} |
| 24 | + if model_id and _NO_SAMPLING_PARAMS_PATTERN.search(model_id): |
| 25 | + return cfg |
| 26 | + cfg["temperature"] = temperature |
| 27 | + return cfg |
| 28 | + |
11 | 29 |
|
12 | 30 | class BedrockClient: |
13 | 31 | def __init__(self, cfg): |
@@ -82,7 +100,7 @@ def invoke(self, system_prompt, user_prompt, max_tokens=4096, |
82 | 100 | kwargs = { |
83 | 101 | "modelId": self._model_id, |
84 | 102 | "messages": [{"role": "user", "content": user_content}], |
85 | | - "inferenceConfig": {"maxTokens": max_tokens, "temperature": temperature}, |
| 103 | + "inferenceConfig": _build_inference_config(max_tokens, temperature, self._model_id), |
86 | 104 | } |
87 | 105 |
|
88 | 106 | if system_prompt: |
@@ -159,10 +177,11 @@ def invoke_with_usage(self, system_prompt, user_prompt, max_tokens=4096, |
159 | 177 | user_content = [{"guardContent": {"text": {"text": user_prompt}}}] |
160 | 178 | else: |
161 | 179 | user_content = [{"text": user_prompt}] |
| 180 | + effective_model = model_id or self._model_id |
162 | 181 | kwargs = { |
163 | | - "modelId": model_id or self._model_id, |
| 182 | + "modelId": effective_model, |
164 | 183 | "messages": [{"role": "user", "content": user_content}], |
165 | | - "inferenceConfig": {"maxTokens": max_tokens, "temperature": temperature}, |
| 184 | + "inferenceConfig": _build_inference_config(max_tokens, temperature, effective_model), |
166 | 185 | } |
167 | 186 | if system_prompt: |
168 | 187 | kwargs["system"] = [{"text": system_prompt}] |
@@ -224,20 +243,21 @@ def converse_with_tools(self, system_prompt, messages, tool_specs, |
224 | 243 | Two cachePoints: one after the system block, one at the tail of the |
225 | 244 | last message (so the growing conversation history is cached |
226 | 245 | turn-over-turn instead of re-priced as fresh input every turn). |
227 | | - Both Opus 4.6 and Haiku 4.5 require ≥4,096 tokens before a cachePoint |
228 | | - for it to take effect; first-turn message-tail caches silently no-op |
229 | | - on small payloads but become useful once tool results accumulate. |
| 246 | + Opus 4.x and Haiku 4.5 require ≥4,096 tokens before a cachePoint for |
| 247 | + it to take effect; first-turn message-tail caches silently no-op on |
| 248 | + small payloads but become useful once tool results accumulate. |
230 | 249 | """ |
231 | 250 | if self._circuit_open: |
232 | 251 | logger.warning("Circuit breaker open, skipping Bedrock tool-use call") |
233 | 252 | return None |
234 | 253 | try: |
235 | 254 | wrapped_messages = self._wrap_first_user_for_guardrail(messages) |
236 | 255 | wrapped_messages = self._append_tail_cache_point(wrapped_messages) |
| 256 | + effective_model = model_id or self._model_id |
237 | 257 | kwargs = { |
238 | | - "modelId": model_id or self._model_id, |
| 258 | + "modelId": effective_model, |
239 | 259 | "messages": wrapped_messages, |
240 | | - "inferenceConfig": {"maxTokens": max_tokens, "temperature": temperature}, |
| 260 | + "inferenceConfig": _build_inference_config(max_tokens, temperature, effective_model), |
241 | 261 | } |
242 | 262 | if tool_specs: |
243 | 263 | kwargs["toolConfig"] = {"tools": tool_specs} |
|
0 commit comments