feat: per-model inference parameters with extended thinking#203
Merged
Conversation
Replaces the global temperature/max_tokens knobs with a per-model
`supportedParams` map keyed by canonical name (temperature, top_p,
top_k, max_tokens, thinking, reasoning_effort, ...). Admins author
which params apply to each model, the runtime translates canonical
names into the provider-native shape (Bedrock/OpenAI/Gemini), and
users can override per-request from a new Settings → Advanced panel.
Extended thinking on Anthropic Bedrock is the headline use case:
- Stored as an int budget per model; runtime wraps it into the
`{type, budget_tokens}` Anthropic request shape under the field
Strands' BedrockConfig actually forwards (`additional_request_fields`,
not the previously-attempted `additional_model_request_fields`).
- Suppresses temperature/top_p/top_k while on (Anthropic constraint).
- Validated up front: budget >= 1024 and < max_tokens, with inline
errors on the admin form, an "unsatisfiable" disabled state on the
user panel when max_tokens drops below the floor, and a final
cross-param safety drop in the merge step so direct API callers
never ship a Bedrock-rejecting request.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three tests in chat-request.service.spec.ts started failing after the new inference_params plumbing because the mock ModelService didn't declare the new method buildChatRequestObject now calls. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses CodeQL log-injection alert (Boise-State-Development/agentcore-public-stack#628) on the warning emitted when a model lookup fails. `model_id` is sourced from the chat request body, so a CRLF in the value could forge extra log lines downstream. Adds a local _sanitize_log helper (mirroring voice_routes._sanitize_log) and applies it to all three new log sites that interpolate model_id — including two where the value reaches us via the managed_model record but originated as user input on create. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
Author
…ng + tool use The persistence-side _filter_empty_text in TurnBasedSessionManager dropped reasoningContent blocks. Anthropic requires the prior thinking block (with its signature) to be replayed verbatim while a tool-use cycle is open; losing it triggers `messages.X.content.Y.thinking.signature: Field required` on subsequent Bedrock calls. Replace the narrow allowlist with the full set of Bedrock Converse content block keys mirrored from Strands' BedrockModel._format_request_message_content, and warn when an unrecognized block is dropped so future Bedrock additions don't fail silently. Add a SessionMessage round-trip test through AgentCoreMemoryConverter that asserts the signature survives JSON serialization. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Phil Merrell <philmerrell@boisestate.edu>
…updates (#149) Bumps the angular group with 10 updates in the /frontend/ai.client directory: | Package | From | To | | --- | --- | --- | | [@angular/cdk](https://github.com/angular/components) | `21.2.5` | `21.2.9` | | [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.7` | `21.2.11` | | [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.7` | `21.2.11` | | [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.7` | `21.2.11` | | [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.7` | `21.2.11` | | [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.7` | `21.2.11` | | [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.7` | `21.2.11` | | [@angular/build](https://github.com/angular/angular-cli) | `21.2.6` | `21.2.9` | | [@angular/cli](https://github.com/angular/angular-cli) | `21.2.6` | `21.2.9` | | [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.7` | `21.2.11` | Updates `@angular/cdk` from 21.2.5 to 21.2.9 - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md) - [Commits](angular/components@v21.2.5...v21.2.9) Updates `@angular/common` from 21.2.7 to 21.2.11 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/common) Updates `@angular/compiler` from 21.2.7 to 21.2.11 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/compiler) Updates `@angular/core` from 21.2.7 to 21.2.11 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/core) Updates `@angular/forms` from 21.2.7 to 21.2.11 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/forms) Updates `@angular/platform-browser` from 21.2.7 to 21.2.11 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/platform-browser) Updates `@angular/router` from 21.2.7 to 21.2.11 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/router) Updates `@angular/build` from 21.2.6 to 21.2.9 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](angular/angular-cli@v21.2.6...v21.2.9) Updates `@angular/cli` from 21.2.6 to 21.2.9 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](angular/angular-cli@v21.2.6...v21.2.9) Updates `@angular/compiler-cli` from 21.2.7 to 21.2.11 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/compiler-cli) --- updated-dependencies: - dependency-name: "@angular/build" dependency-version: 21.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/cdk" dependency-version: 21.2.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/cli" dependency-version: 21.2.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/common" dependency-version: 21.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/compiler" dependency-version: 21.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/compiler-cli" dependency-version: 21.2.8 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/core" dependency-version: 21.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/forms" dependency-version: 21.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/platform-browser" dependency-version: 21.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/router" dependency-version: 21.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the infra-minor-patch group with 1 update in the /infrastructure directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 25.5.2 to 25.6.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.6.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: infra-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ingestion - Add xlsx_chunker.py module to convert Excel sheets to CSV and chunk by rows - Integrate XLSX detection and routing in docling_processor.py to bypass Docling's slow table parsing - Prepend sheet names to chunks for multi-sheet workbooks to preserve context in embeddings - Skip empty sheets and handle None values during row conversion - Use existing row-based CSV chunker for consistent token-based chunking across tabular formats - Improves performance and memory usage for Excel files while maintaining header-per-chunk structure for better RAG embeddings
- Replace single-batch vector upload with batched processing (50 vectors per batch) - Add batch size constant to prevent exceeding S3 Vectors request body limits - Replace print statement with logger.info for consistent logging - Add progress logging at 500-vector intervals and batch completion - Improve docstring to clarify batch processing behavior - Refactor vector payload construction to use list comprehension within batch loop - Prevents request failures when storing large numbers of embeddings in a single operation
- Add 'backend/src/apis/shared/embeddings/**' to push trigger paths - Add 'backend/src/apis/shared/embeddings/**' to pull_request trigger paths - Ensures RAG ingestion workflow runs when shared embeddings module changes
- Add _is_likely_header() helper to identify real column headers using heuristics (text-dominant cells, non-empty threshold) - Add _find_header_row_index_from_rows() to locate the first actual header row, skipping sparse title/banner rows at sheet start - Refactor chunk_xlsx() to collect all non-empty rows first, then detect header offset before CSV conversion - Update logging to report actual data row count instead of total non-empty rows - Improves chunking accuracy for XLSX files with metadata, titles, or banner rows before the actual data table
…isn't orphaned (#207) * fix(chat): align resume cache key with original turn so paused agent isn't orphaned The agent cache keyed on the unbuilt `system_prompt` parameter, but the construction snapshot persisted the *built* prompt. Resume requests passed the built form back into `get_agent`, hashing to a different cache slot than the original turn. The resume rebuilt a fresh agent (cache MISS), leaving the original (paused) agent stuck under the original key. The next non-resume turn then cache-hit the paused agent, and Strands raised "prompt_type=<class 'str'> | must resume from interrupt with list of interruptResponse's". Snapshot the unbuilt prompt so resume hashes to the same key as the original turn. Add defense-in-depth: when `get_agent` cache-hits a paused agent on a non-resume request, evict and rebuild instead of serving the stale state. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(chat): cover OAuth-resume cache fix and clean up eviction helper - Add `tests/apis/inference_api/test_chat_service.py` exercising the `get_agent` cache behavior the fix depends on: snapshot replay lands on the same cache slot, paused-agent eviction fires on non-resume, resume preserves the paused agent, healthy agents still cache-hit. - Extract `_is_paused_on_interrupt` helper. `getattr` with defaults cannot raise, so the broad try/except around the check was dead and hid real bugs behind a `logger.exception`. Replace with a direct call. - Expand the snapshot comment in `BaseAgent.__init__` to flag the cache-evicted-resume date-drift trade-off (rebuilt agent re-renders `Current date:` rather than preserving the original turn's date). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…208) MessageMetadata.cost is Optional[Union[float, Dict[str, float]]]. The streaming path produces a breakdown dict ({"total": ..., "inputCost": ...}), which flowed through `cost = message_metadata.cost or 0.0` unchanged and hit `Decimal(str(cost_delta))` in the DynamoDB summary writer, raising decimal.InvalidOperation. The per-message cost record was unaffected (its dict goes through _convert_floats_to_decimal recursively); only the rollup path crashed, leaving the summary silently stale. Two layers of defense: - Upstream: _coerce_cost_total normalizes dict/float/None/NaN/inf to a finite float total before the summary call. Also use `or 0` (not .get(..., 0)) on pricing_dict reads, since managed-model rows can store an explicit None for cache_read pricing. - Boundary: _safe_decimal in dynamodb_storage replaces every Decimal(str(cost_delta | cache_savings_delta)) site (5 total) so a bad value collapses to Decimal("0") instead of aborting the rollup.
…des, fix host listener Review follow-ups for #203: - Remove `lastTemperature` from SessionPreferences/UpdateSessionMetadataRequest and the writer in stream_coordinator. The read site was reaching for a ModelConfig field that no longer exists, so every turn was overwriting the stored value with None. With per-model inference-param overrides now persisted in sessionStorage, the field is redundant rather than just broken — drop it. Existing DDB rows are silently ignored on read via Pydantic `extra="allow"`. - Gate the request-side passthrough in `_merge_inference_params` against a new `KNOWN_CANONICAL_PARAMS` allow-list (union of all provider mapping keys). Without this, a user could submit a future canonical key the admin hasn't yet bounded — or one a future provider mapping starts forwarding — and bypass per-model bounds. - Replace `@HostListener` in model-settings with the `host` decorator map per the frontend project conventions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Phil Merrell <philmerrell@boisestate.edu>
The flag had no behavioral consumer — every reference was a schema definition, CRUD passthrough, or form binding. Reasoning capability is now expressed per-param via `supportedParams.thinking` (Anthropic) and `supportedParams.reasoning_effort` (OpenAI o-series), so a UI badge that wants to show "reasoning model" can derive it from the spec. Pydantic v2 defaults to `extra="ignore"` on ManagedModel, so existing DDB rows with `isReasoningModel: false` load cleanly post-removal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Phil Merrell <philmerrell@boisestate.edu>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
temperature/max_tokensknobs with a per-modelsupportedParamsmap keyed by canonical name (temperature,top_p,top_k,max_tokens,thinking,reasoning_effort, …). Admins author it on the model form, the runtime translates canonical names into provider-native shapes (Bedrock / OpenAI / Gemini), and users can override per-request from a new Settings → Advanced panel.thinkingvalue is an int budget that the translator wraps into{type: "enabled", budget_tokens: N}under the field Strands'BedrockConfigactually forwards (additional_request_fields, not the originally-attemptedadditional_model_request_fields). Sampling params that conflict with thinking are suppressed before dispatch.min ≤ max,default ∈ [min, max]), thinking-specific FormArray validator (budget ≥ 1024,budget < max_tokens.default), PydanticSupportedParams._check_thinking_invariantson save, and a final cross-param safety drop in_merge_inference_paramsat request time.Backend
backend/src/apis/shared/models/models.py— newModelParamSpec+SupportedParamstypes added toManagedModel/Create/Update._check_thinking_invariantsmodel_validator catches budget < 1024 / >= max_tokens at config time.backend/src/agents/main_agent/core/model_config.py— per-provider canonical → native translation tables, dot-path nested writes, thinking value shaping, and incompatible-sampling-param suppression.top_kandthinkingroute throughadditional_request_fieldsfor Bedrock.backend/src/apis/inference_api/chat/routes.py—_merge_inference_paramslayers admin defaults + request overrides, respectslocked: true(admin default wins), clamps numerics to bounds, and now dropsthinkingif>= max_tokensafter the merge so direct API callers can't trigger aValidationExceptionfrom Bedrock.backend/src/apis/inference_api/chat/models.py—InvocationRequest.inference_paramsfield.backend/scripts/seed_bootstrap_data.py— seeds default Claude chat models with atemperature/top_p/max_tokenssupportedParamsblock.Frontend
frontend/ai.client/src/app/admin/manage-models/model-form.page.{ts,html}— full editor forsupportedParamswith per-row + cross-row validators (paramRowBoundsValidator,thinkingInvariantsValidator), inline error display, and a newthinkingBudgetinput kind. Save button is gated onmodelForm.invalidso bad rows can't ship.frontend/ai.client/src/app/components/model-settings/model-settings.{ts,html}— collapsible Advanced section that renders inputs fromselectedModel().supportedParams. Bounds are merged from catalog defaults + provider-specific bounds + admin spec; the thinking row's max is tightened tomax_tokens − 1and the toggle is disabled with an explanation when that window collapses below 1024. Auto-clears the thinking override if max_tokens drops below the budget. Transient amber clamp notices appear when typed values get coerced.frontend/ai.client/src/app/admin/manage-models/models/managed-model.model.ts—ModelParamSpec,SupportedParams,KNOWN_PARAMScatalog, and the newkind: 'thinkingBudget'+incompatibleWithfields.frontend/ai.client/src/app/session/services/model/model.service.ts—_inferenceOverridessignal keyed by modelId, persisted in sessionStorage.frontend/ai.client/src/app/session/services/chat/chat-request.service.ts— sendsinference_paramson the chat request body when overrides exist.Test plan
cd backend && uv run python -m pytest tests/agents/main_agent/core/test_model_config.py tests/agents/main_agent/property/ tests/agents/main_agent/test_fixtures_smoke.py(76 passing, includes new tests for the Bedrock thinking shape, top_k routing, sampling-param suppression, and disabled-thinking passthrough)cd frontend/ai.client && npx ng build --configuration development(template type-check clean)temperature/top_p/max_tokens/thinkingwith bad bounds (min > max,defaultout of range,thinking < 1024,thinking >= max_tokens); verify each shows an inline red bullet and Save is disabled.default: 4096andmax_tokens: 8192. Confirm DDB round-tripssupportedParams.additional_request_fields.thinkingreaches Bedrock).sessionStorage.inferenceParamOverridesdirectly to{"<modelId>": {"max_tokens": 1000, "thinking": 1024}}, send a prompt. Should succeed without reasoning, withDropping thinking budget 1024 ... not less than max_tokens 1000warning in inference-api logs.Notes / follow-ups
thinkingcontent blocks (withsignature) across tool-use turns, enabling thinking on a tool-using prompt will 400 from Bedrock on the second call. Worth a separate ticket — verify behavior before turning thinking on for any prod model.ModelParamSpec.lockedand the_inferenceOverridessignal — this PR is the admin-authored side; locked-respecting user UX is built but only exposed via the Advanced panel today.🤖 Generated with Claude Code