Repository navigation
Conversation
model.parameters is a free-form dict the LaunchDarkly UI writes for
provider tuning values, including max_turns for agent turn caps. Every
Python handler except claude-messages ignored it entirely, and
claude-messages read only max_tokens. In particular there was no way
to cap an agent's turns: claude-agents never set
ClaudeAgentOptions.max_turns and openai-agents never passed max_turns
to Runner.run.
Adds a shared launchdarkly_ai_server.model_parameters(config) helper
that returns model.parameters as a fresh dict (or {} when absent/not
a mapping), and never reads model.custom. Applies it at every provider
call site across all six handler packages, including streaming and
native-graph paths. Keys are forwarded as-is (snake_case, matching
both the UI and every Python provider SDK here); no allowlisting and
no case conversion.
Handler-owned keys (model, messages/input, system/instructions, tools,
etc., decided per call site) are always removed from the forwarded
dict before merging with the handler's own kwargs, so a config value
can never override what the handler itself sets. claude-messages keeps
its max_tokens-defaults-to-1024 behaviour exactly.
LangChain's handlers already forwarded model.parameters through a
local _model_constructor_kwargs helper; swapped that to use the new
shared helper for consistency.
Verified: uv run pytest (1332 passed, 11 skipped), uv run mypy
packages/*/src, uv run ruff check ., uv run ruff format --check .
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run |
…ccepts model.parameters is a free-form dict the LaunchDarkly UI writes, offering keys that make sense across providers but that no single provider SDK accepts in full. Forwarding it unfiltered (added in a prior commit on this branch) raised TypeError before any request for several real configs: ClaudeAgentOptions has no temperature/top_p/top_k/max_tokens/ stop_sequences/tool_choice/metadata; the OpenAI Responses API has no max_tokens/frequency_penalty/presence_penalty/seed/n/stop/response_format/ logit_bias/logprobs/max_completion_tokens/audio/modalities/prediction; Anthropic's Messages API has no top-level effort. Adds launchdarkly_ai_server.parameter_forwarding, a shared filter used by every handler: accept-sets are derived once from the live provider type (inspect.signature for plain methods, dataclasses.fields for ClaudeAgentOptions/ModelSettings, pydantic model_fields + aliases for the LangChain chat models), never hand-listed, so an SDK's own drift is picked up automatically. transport/escape-hatch keys (extra_headers, extra_query, extra_body, timeout, extra_*) are stripped unconditionally regardless of what a signature accepts, and each call site may additionally exclude keys the API accepts but that would break the handler because the handler itself already decides that behaviour: stream/stream_options/background/ conversation/prompt for the OpenAI Responses API, stream for Anthropic Messages. Everything else the provider accepts is forwarded, including keys that are not strictly generation settings (store, user, safety_identifier, prompt_cache_key, prompt_cache_retention, include, context_management, metadata, service_tier, instructions, moderation). Two renames, applied before the accept filter: openai-messages maps max_tokens/max_completion_tokens to the Responses API's max_output_tokens (explicit max_output_tokens wins, then max_completion_tokens, then max_tokens); claude-messages maps a top-level effort into output_config.effort, unless the config already sets its own output_config.effort, which wins. Applied at every call site across all six handlers, including streaming and native-graph paths. claude-agents/openai-agents dataclass accept-sets are computed once at module load from the real SDK types, independent of each package's per-call importlib.import_module mocking in tests. LangChain's per-provider pydantic accept-sets are cached per class with functools.cache, since the constructor class is only known once a provider branch resolves langchain_openai/_anthropic/_aws. Updated the existing tests whose old assertions relied on unfiltered pass-through of a key the real SDK does not accept (tools forwarded to ChatOpenAI's constructor in two langchain-agents/messages tests) to give the mocked provider class an accurate model_fields shape and to reflect the value now being dropped. Verified: uv run pytest (1383 passed, 11 skipped), uv run mypy packages/*/src, uv run ruff check ., uv run ruff format --check .. uv.lock unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run |
…warded-key lists Every handler decided which model.parameters keys to forward by introspecting the live provider SDK at runtime: inspect.signature for the two Anthropic/OpenAI methods, dataclasses.fields for ClaudeAgentOptions/ModelSettings, pydantic model_fields plus aliases for the three LangChain chat models. Every competitor SDK (LiteLLM, Vercel AI SDK, Braintrust, LangChain) and this SDK's own TypeScript port use a written-down list instead, so replaces the derivation with one: each handler now carries a literal frozenset of the keys it forwards, generated once from the same introspection and pasted in as a reviewable list, next to its existing handler-owned and excluded sets. Deletes accepted_parameter_keys_from_signature/_from_dataclass/_from_pydantic_model, _alias_strings, is_transport_parameter, strip_transport_parameters, and filter_forwardable_parameters from packages/client's parameter_forwarding module now that nothing calls them. In their place, select_forwarded_parameters(params, keys) is the one shared piece left: keep the params keys that are on a handler's own forwarded list, drop everything else. Reproduces every handler's existing forwarded/owned/excluded classification exactly, with one deliberate addition applied consistently: client/connection configuration (API keys, base URLs, organization ids, HTTP clients, default headers/query, proxies, timeouts, retry counts) is now always excluded, even when a provider's own accept-set would otherwise let it through. This newly excludes, per handler: - langchain-messages / langchain-agents (ChatOpenAI): api_key, openai_api_key, base_url, openai_api_base, organization, openai_organization, openai_proxy, client, async_client, root_client, root_async_client, http_client, http_async_client, http_socket_options, default_headers, default_query, max_retries, request_timeout - langchain-messages / langchain-agents (ChatAnthropic): anthropic_api_key, api_key, anthropic_api_url, base_url, anthropic_proxy, default_request_timeout, max_retries, default_headers - langchain-messages / langchain-agents (ChatBedrockConverse, opt-in dependency): bedrock_api_key, api_key, aws_access_key_id, aws_secret_access_key, aws_session_token, credentials_profile_name, endpoint_url, base_url, client, bedrock_client, config, default_headers, max_retries - openai-agents (ModelSettings): retry timeout/extra_headers/extra_query/extra_body/extra_args were already excluded by the prior transport-prefix rule on every handler that has them; they are now named explicitly in each handler's own excluded set instead of matched by a shared prefix check, since nothing here is derived at runtime any more. Adds one drift test per handler package (test_parameter_forwarding.py) that reads the real provider SDK's signature/dataclass/model_fields and asserts every parameter it accepts is classified in exactly one of forwarded, handler-owned, or excluded, so an SDK addition nobody has classified fails loudly by name, and so does a stale list entry. The LangChain ChatBedrockConverse test skips itself when langchain-aws (an opt-in dependency) is not installed, matching this package's existing Bedrock test pattern. Updates langchain-messages' TestModelParametersReachTheWire test, which relied on forwarding http_async_client/api_key through model.parameters to mock the outgoing HTTP call: it now intercepts ChatOpenAI's own default httpx client builder instead, so the mock transport is reached without any config value ever naming an HTTP client or a key. Adds TestConnectionConfigIsNeverForwarded to both langchain-messages and langchain-agents, asserting api_key/base_url from model.parameters never reach the ChatOpenAI/ChatAnthropic constructor kwargs. Verified: uv run --frozen pytest (1385 passed, 15 skipped), uv run --frozen mypy packages/*/src, uv run --frozen ruff check ., uv run --frozen ruff format --check ., uv.lock unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run |
…eters They are the same constructor field as `model`, under its field name or its alias. The handler always sets `model` itself, so a config value for either collided with the resolved model name or overrode it. Classify both as handler-owned per model class, and have the drift tests read that set from the handler instead of a test-local copy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run |
jeffdupont
left a comment
There was a problem hiding this comment.
Reviewed against the 1.0 GA plan. The fix is needed: parameters set in an AI Config were being ignored, and that should be fixed before 1.0. Tests pass at d51199d (make test: 1391 passed, 15 skipped, exit 0). The branch merges cleanly onto current main (fee904a), and the merged tree passes too: 1491 passed, make typecheck clean.
Four things I think need settling before merge, because each one changes the public interface and can't be changed after 1.0:
- Claude Agents forwards host-process settings, not just model settings. A config can set
cli_path,env,cwd,add_dirs,permission_mode,settings,setting_sources,pluginsandsandbox. I reproduced it: a config withcli_path: /tmp/attacker-binarymakes the SDK launch that file as its agent process (claude_agent_sdk/_internal/transport/subprocess_cli.py:225), andenv: {ANTHROPIC_BASE_URL: ...}points the customer's API key at another host (:434). That turns "can edit an AI Config" into "can run code on the customer's server." Inline comment below. - Two LangChain keys get around the exclusion list.
model_kwargscarriesextra_headers/extra_queryinto the ChatOpenAI request payload (reproduced with_get_request_payload), though both are listed as excluded.mcp_servers(ChatAnthropic) would let a config attach a remote MCP server; I haven't tested that one. - Two new public names.
model_parametersandselect_forwarded_parametersare in the root__all__, but only the provider packages use them. Same point as on #121 withmake_graph_track_data: they belong in the internal group, or the 1.0 surface trim has to remove them again. - Python and JS don't match (js #73). The two messages handlers agree. In the agent and LangChain handlers, JS forwards the whole camelCased bag with no allowlist, so
api_keybecomesapiKey, which ChatOpenAI and ChatAnthropic use. I'll leave a note there too. The monorepo has no spec for forwarding these parameters, and open monorepo #10 says "do not rename or filter parameter keys," which contradicts both PRs. One spec inTESTING.mdwould settle it for both languages.
Smaller points, not blocking:
output_format(Claude Messages) andresponse_id/starting_after/text_format(OpenAI) are forwarded on stream but not invoke, so one config can behave differently depending on the call.- OpenAI Responses forwards
reasoningbut notreasoning_effort. If the UI writesreasoning_effort, it's dropped silently, aseffortwas before your rename. I haven't checked what the UI writes. - The loops that remove handler-owned keys never find anything, since those keys aren't on the forwarded lists. They're harmless, just dead code.
- In native graphs only the root node's
max_turnsis used.
Removing _HAS_ANTHROPIC is fine; nothing on main reads it.
|
bugbot run |
…d_parameters from the package root Only the provider packages in this repo use these helpers, and anything in __all__ becomes public API at 1.0. Callers now import them from the modules that define them: launchdarkly_ai_server.utils and launchdarkly_ai_server.parameter_forwarding.
|
bugbot run |
…rameters ClaudeAgentOptions also configures the host process the SDK launches: which binary runs, its environment, working directory, file access, permission mode, settings, plugins, sandbox, and session state. Forwarding those from a config let anyone who can edit an AI Config launch their own binary or point the API key at another host. The forwarded list is now max_turns, max_thinking_tokens, thinking, effort, max_budget_usd, fallback_model, output_format and betas. Every other field is excluded, with the reason next to it. The loops that popped handler-owned keys are removed: none of those keys is on the forwarded list, so the filter already drops them.
model_kwargs is merged straight into the request payload, so it carried extra_headers and extra_query past the exclusions. mcp_servers let a config attach a remote MCP server that then receives the conversation. region_name and inference_geo move where data is processed. Bedrock's additional_model_request_fields is another unfiltered request bag. All of those are now excluded, along with LangChain's own runtime fields (callbacks, cache, tags, metadata, streaming mode, message format, ...), which configure how LangChain runs in this process rather than the request. _model_constructor_kwargs now requires its forwarded list, and the dead pop of tools for Bedrock is removed.
output_format is only accepted by messages.stream, so a config with it behaved differently depending on the call. It is now dropped on both paths; output_config, which both accept, carries the output format. One forwarded list now serves both calls. inference_geo is excluded too: it sets the region inference runs in. The loops that popped handler-owned keys are removed, since none of those keys is on the forwarded list.
…hropic ChatAnthropic takes both spellings of one field, so a config that set both left the winner to pydantic. The alias is now renamed to max_tokens before forwarding, and a max_tokens the config also set wins.
|
bugbot run |
…sity win A forwarded reasoning object becomes the SDK's own Reasoning type with only effort and summary, the same two sub-keys the JS SDK keeps, and is dropped when it has neither. A test runs the real Chat Completions model with it. When a config sets both text.verbosity and a top-level verbosity, the explicit text.verbosity now wins, matching the JS SDK.
ChatOpenAI has no prompt_cache_key field, so the key is cut from the cross-SDK ChatOpenAI list rather than worked around. It is documented on the excluded side with that reason, and a test checks a config value never reaches the constructor.
|
bugbot run |
|
bugbot run |
jeffdupont
left a comment
There was a problem hiding this comment.
Re-reviewed at db839fe5. The credential and redirect work is done. No endpoint, key or header setting reaches a provider from any handler, and the tests prove it. Removing the filter fails 99 tests, and putting single keys back fails 5 to 10 each. The discovery test also fires: a trial merge with py #103 or py #102 goes red until they add allowlists. All seven Python lists match the monorepo #42 §1.12 tables. Suite: 1721 passed, 11 skipped, mypy and ruff clean; merged with main it's 2570 passed.
Three things before I'd approve:
-
Two malformed settings now crash a run (inline). Spec §1.12 says a nested object without a
typeis dropped, and js #118 drops them (isTypedObject).- Claude Agents:
thinking: {"budget_tokens": 1024}raisesKeyError: 'type'inclaude_agent_sdk0.2.110's command builder, because it readst["type"]directly.{"type": "enabled"}with no budget raisesKeyError: 'budget_tokens'. Before this PR nothing was forwarded, so these configs ran. - OpenAI Agents:
tool_choice: {"type": "auto"}makesModelSettings(**...)raise a pydanticValidationError(handler.py:332). JS drops a non-stringtoolChoice. - The same gap without a crash: Claude Messages forwards
thinking,tool_choiceandcache_controlobjects without atypeunchanged, and openai-messages passes unknownreasoningsub-keys through. §1.12 keeps onlyeffortandsummary.
The probe tests didn't catch any of this because
probe_forwarded_keyscounts an exception as "forwarded" (inline). - Claude Agents:
-
The handlers need a lower bound on
launchdarkly-ai-server. Six handler packages now importlaunchdarkly_ai_server.parameter_forwardingandutils.model_parameters, but declare a bare"launchdarkly-ai-server". PyPI's latest is 0.3.0, which has neither, so importinglaunchdarkly_ai_claude_messagesagainst it fails withModuleNotFoundError. A>=on the client release that ships these modules, in each handler'spyproject.toml, fixes it. (Not in this diff, so no inline.) -
Merge order. The 4 open threads (
inference_geo/container,use_responses_api, Bedrocksystem, openai-agents parity) are all done in Python. They resolve once js #118 and monorepo #42 land with this PR. Both are drafts, and #42 has no human review yet.
Not blocking: with both stop and stop_sequences set, ChatAnthropic sends stop here and JS sends stop_sequences. The spec gives no precedence for the alias pairs, so that's a spec question for #42.
|
|
||
| #: Forwarded keys whose value must be an object. A config value of any other type is malformed | ||
| #: and dropped rather than passed to ``ClaudeAgentOptions``. | ||
| _CLAUDE_AGENT_OPTIONS_MAPPING_KEYS = frozenset({"output_format", "thinking"}) |
There was a problem hiding this comment.
This checks that thinking is a mapping but not its shape. claude_agent_sdk 0.2.110 reads t["type"] and then t["budget_tokens"] straight away when it builds the CLI command. So {"budget_tokens": 1024} raises KeyError: 'type', and {"type": "enabled"} raises KeyError: 'budget_tokens'. Both configs ran before this PR. Could thinking be dropped unless it has a known type (and budget_tokens for enabled), as §1.12 and js #118 do?
| name="assistant", | ||
| model=config.get("model", {}).get("name", "gpt-4o"), | ||
| **( | ||
| {"model_settings": agents_mod.ModelSettings(**model_settings_params)} |
There was a problem hiding this comment.
tool_choice: {"type": "auto"} reaches here and ModelSettings(**model_settings_params) raises a pydantic ValidationError (4 errors, reproduced). JS drops a non-string toolChoice. Dropping it in _model_settings_parameters when it isn't a string would match.
| #: Forwarded keys whose value must be an object. A config value of any other type is malformed | ||
| #: and dropped rather than passed to the API. | ||
| _MESSAGES_MAPPING_KEYS = frozenset( | ||
| {"cache_control", "output_config", "thinking", "tool_choice"} |
There was a problem hiding this comment.
These are checked for being mappings only, so thinking: {"budget_tokens": 1}, tool_choice: {} and cache_control: {} are forwarded unchanged. §1.12 drops a nested object without a type, and js #118 does (isTypedObject). It doesn't crash here, but the Anthropic API will reject it.
|
|
||
| #: Forwarded keys whose value must be an object. A config value of any other type is malformed | ||
| #: and dropped rather than passed to the API. | ||
| _RESPONSES_MAPPING_KEYS = frozenset({"reasoning"}) |
There was a problem hiding this comment.
reasoning is forwarded as-is, so {"effort": "low", "generate_summary": "auto", "x": 1} reaches the request. §1.12 keeps only effort and summary, and js #118's toReasoning does the same.
| for key in sorted(set(candidates)): | ||
| try: | ||
| result = await call({key: sample(key)}) | ||
| except Exception: |
There was a problem hiding this comment.
Counting an exception as "forwarded" hides the crashes above. SAMPLE_VALUES["tool_choice"] is exactly the object that makes ModelSettings raise, so the probe reports it as forwarded and passes. Could an exception fail the probe instead, or be reported separately?
Some libraries take a different valid shape for the same key. Claude Messages takes tool_choice as an object, while the OpenAI Agents model settings over LiteLLM and the Python Vercel AI SDK take only a string. The probe now accepts per-handler overrides, so each handler can be probed with a value its own library accepts. Also adds valid samples for max_tool_calls, reasoning_effort and seed, which the Vercel handlers forward and type-check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orwarded bag litellm.acompletion takes **kwargs and reads its own names for endpoints, credentials, routing and retries from them: api_base, api_version, deployment_id, custom_llm_provider, model_list, fallbacks, num_retries, shared_session, vertex_credentials and aws_region_name. mock_response skips the model call entirely. A config value under any of these names could redirect the request or swap the credentials, so every handler's probe and bag test now checks them too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The handler already kept an allowlist, but under its own name and with a hand-written filter, so the never-forwarded check could not find it. The same 11 keys are now _COMPLETION_FORWARDED_KEYS, applied with select_forwarded_parameters, and registered with the cross-handler test. Every named acompletion parameter is now classified as forwarded, handler-owned or excluded, with the reason for each exclusion, and a drift test fails when litellm adds one nobody has classified. A probe checks that invoke and stream forward exactly this list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The handler forwarded store, which is data retention and on the never-forwarded list, and verbosity, which LitellmModel never reads and so silently dropped. Both are gone. The list is now _MODEL_SETTINGS_FORWARDED_KEYS, applied with select_forwarded_parameters and registered with the cross-handler test: the eight ModelSettings fields LitellmModel actually sends, plus max_turns for the run. reasoning keeps only effort. LitellmModel sends it as reasoning_effort and warns on every call that also sets summary. tool_choice is forwarded only as a string, because ModelSettings rejects an object value while the Agent is built, which turned a config edit into a failed run. The camelCase aliases are gone too. Every other Python handler reads snake_case only, which is what the LaunchDarkly UI writes. A drift test classifies every ModelSettings field and checks each forwarded one is read by LitellmModel, and a probe checks invoke and stream forward exactly this list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aph run The native graph built every node's ModelSettings from the handler's list but never passed max_turns to Runner.run, so a graph always ran with the SDK default whatever the root config said. The whole graph is one run and the SDK counts turns once across every handoff, so the root's value applies, as in the OpenAI Agents native graph. A probe of the root node now checks the graph forwards exactly the LiteLLM Agents list, and a bag test checks it forwards no never-forwarded key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The handler copied model.parameters, removed a set of owned names, and mapped what was left. That forwarded metadata and safety_identifier, which are identity and attribution and on the never-forwarded list, and min_p and repetition_penalty, which every built-in provider rejects, so setting one failed every call. It also dropped frequency_penalty, presence_penalty, max_output_tokens, max_completion_tokens and tool_choice. The list is now _INFERENCE_PARAMS_FORWARDED_KEYS, applied with select_forwarded_parameters after the renames: max_output_tokens wins over max_completion_tokens, which wins over max_tokens, and reasoning wins over reasoning_effort. Each key is rebuilt as the library's typed param. reasoning is a string or an object read for its effort, and "none" becomes effort None, the library's way to turn reasoning off. tool_choice is forwarded only as auto, none or required, as ToolChoiceMode; an object is dropped. Each SDK forwards what its own library accepts, so this list also carries parallel_tool_calls, max_tool_calls, service_tier and prompt_cache_key, which the Python library takes as typed params and the JS AI SDK cannot set outside providerOptions. stop_sequences stays off: the Python library has no field for it. The camelCase aliases and the owned-names set are gone, since an allowlist already drops everything else. The test that asserted metadata and safety_identifier reach the provider now asserts they do not. A drift test classifies every InferenceRequestParams field, and a probe checks invoke and stream forward exactly this list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The handler and the native graph shared one params builder with the same problems as vercel-messages: it forwarded metadata and safety_identifier (identity and attribution), min_p and repetition_penalty (every built-in provider rejects them), accepted camelCase aliases, and dropped frequency_penalty, presence_penalty, max_output_tokens, max_completion_tokens and tool_choice. The builder now forwards _INFERENCE_PARAMS_FORWARDED_KEYS with the same renames and typed rebuilds as vercel-messages, including the keys the Python library takes natively and the JS AI SDK cannot: parallel_tool_calls, max_tool_calls, service_tier and prompt_cache_key. The native graph calls the same builder for every node, so it forwards the same list. The owned-names set is gone, the leak test now asserts metadata and safety_identifier never reach the provider, and probes check invoke, stream and the native graph forward exactly this list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenCode's message route takes no sampling, length, stop, seed, tool-choice or reasoning field. Those come from the OpenCode server's own agent and provider config, which the handler does not control per call. The fields the route does take are handler-owned (parts, model, system, agent, tools), change where the answer lands (format), wire the run (noReply), name session state (messageID), or name a server-side preset whose meaning depends on the operator's config (variant). So the handler forwards nothing, and now says so with an empty _PROMPT_FORWARDED_KEYS applied with select_forwarded_parameters ahead of the handler's own body fields. It is registered with the cross-handler test, which no longer reports any handler module without a list, and a probe checks no model.parameters key changes the posted body on invoke or stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The imports this branch added for select_forwarded_parameters and model_parameters sat out of order in nine modules, so make lint failed. This is ruff's own fix, with no change in behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Commit 8c1f9f7 cut max_tool_calls only because the JS openai SDK's responses.create has no such parameter. The spec now keeps a key that one language's library cannot take and marks it not supported there, so the Python handler forwards it again. Checked against openai 2.44.0 in the uv env: AsyncResponses.create and AsyncResponses.stream (openai/resources/responses/responses.py) both take max_tool_calls, so invoke and stream still forward the same keys. The key goes back on _RESPONSES_FORWARDED_KEYS and OPENAI_MESSAGES, and off the excluded list. uv run pytest -q packages/openai-messages: 133 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
seed came off the ChatOpenAI list in 1c60bdf with no reason beyond not being on the cross-SDK list, which had cut it because the JS ChatOpenAI takes seed only as a per-call option. The spec now keeps a key that one language's library cannot take where the handler sets it and marks it not supported there, so both Python LangChain handlers forward it again. Checked against langchain-openai 1.3.3 in the uv env: ChatOpenAI declares seed as a constructor field (langchain_openai/chat_models/base.py) and _default_params sends it on every Chat Completions request. It is not one of the fields _use_responses_api switches on. The JS side was checked in @langchain/openai 1.4.7: seed appears only in BaseChatOpenAICallOptions and callKeys, not on the constructor fields. uv run pytest -q packages/langchain-agents packages/langchain-messages: 366 passed, with the probes asserting the ChatOpenAI list exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
reasoning_effort came off the ChatOpenAI list in 1c60bdf so a config could not set reasoning by one spelling and not the other, and the spec held reasoning control back until both SDKs could set it without switching APIs. The JS ChatOpenAI takes reasoningEffort only as a per-call option, so that was a parity cut. The spec now marks such a key not supported in the language that cannot take it, so both Python LangChain handlers forward it again. reasoning itself stays excluded, because it does switch APIs. Checked against langchain-openai 1.3.3 in the uv env (langchain_openai/chat_models/base.py): reasoning_effort is a constructor field documented for the Chat Completions API, _default_params sends it, and neither ChatOpenAI._use_responses_api nor the module-level _use_responses_api reads it (they check reasoning, include, truncation, context_management, text and previous_response_id). A new test in each package builds a real ChatOpenAI from the forwarded kwargs and asserts the payload is a Chat Completions one carrying reasoning_effort. uv run pytest -q packages/langchain-agents packages/langchain-messages: 368 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…drockConverse again stop, stop_sequences and reasoning_effort came off the ChatBedrockConverse list in 1c60bdf because the spec cut them for both SDKs: the JS ChatBedrockConverse takes stop only as a per-call option and has no reasoning effort field. The spec now marks such a key not supported in the language that cannot take it, so both Python LangChain handlers forward them again. Checked against langchain-aws 1.7.9 in the uv env (langchain_aws/chat_models/bedrock_converse.py): stop_sequences is a constructor field with the alias stop, sent as inferenceConfig.stopSequences, and reasoning_effort is a typed constructor field that the class translates per model family (langchain_aws/utils.py reasoning_effort_additional_fields), checking the value against the model profile. The reasons the handler gave for leaving them out do not hold for this handler: the stop merge it named is in the prompt_prefill structured-output path, and the handler calls with_structured_output with the default function_calling method. The class rejects a call that passes both stop and stop_sequences, so stop is renamed to stop_sequences before forwarding and stop_sequences wins, the same way ChatAnthropic handles max_tokens_to_sample. New tests cover the rename and build a real ChatBedrockConverse from the forwarded kwargs. output_config stays excluded, for a reason other than parity: it only carries Bedrock's JSON schema output format, which the handler owns through outputFormat, and a constructor value would be sent alongside the forced tool call the handler's structured-output turn makes. uv run pytest -q packages/langchain-agents packages/langchain-messages: 374 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ChatOpenAI exclusion comment and its test still said the key was cut from the cross-SDK list. The spec now keeps it and marks it not supported in Python, since ChatOpenAI has no such constructor field, and the JS handler forwards it. Comment and docstring only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each SDK now forwards what its own library accepts, so the JS SDK is no longer held to the same lists. The docstring said it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 172a7ac. Configure here.
|
|
||
| #: Forwarded keys whose value must be an object. A config value of any other type is malformed | ||
| #: and dropped rather than passed to ``ClaudeAgentOptions``. | ||
| _CLAUDE_AGENT_OPTIONS_MAPPING_KEYS = frozenset({"output_format", "thinking"}) |
There was a problem hiding this comment.
Incomplete thinking objects crash Claude Agents
High Severity
thinking is kept whenever the value is a mapping, so a value missing type, or budget_tokens when type is enabled, is passed into ClaudeAgentOptions. The Claude Agent SDK then raises KeyError while building the CLI command. Those configs were ignored before this change and now fail the run.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 172a7ac. Configure here.
| name="assistant", | ||
| model=config.get("model", {}).get("name", "gpt-4o"), | ||
| **( | ||
| {"model_settings": agents_mod.ModelSettings(**model_settings_params)} |
There was a problem hiding this comment.
Object tool_choice crashes OpenAI Agents
High Severity
_model_settings_parameters forwards tool_choice unchanged. An object value, the shape the UI and the shared sample use, is passed to ModelSettings and raises a pydantic ValidationError while the agent is built. LiteLLM Agents already drops a non-string tool_choice for this reason.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 172a7ac. Configure here.
| #: and dropped rather than passed to the API. | ||
| _MESSAGES_MAPPING_KEYS = frozenset( | ||
| {"cache_control", "output_config", "thinking", "tool_choice"} | ||
| ) |
There was a problem hiding this comment.
Untyped nested objects still forwarded
Medium Severity
thinking, tool_choice, and cache_control are kept whenever the value is a mapping. An object without a type still reaches the Anthropic API, which rejects the request. The shared filter only checks the top-level type, so previously ignored incomplete objects now fail the call.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 172a7ac. Configure here.
| result = await call({key: sample(key, overrides)}) | ||
| except Exception: | ||
| forwarded.add(key) | ||
| continue |
There was a problem hiding this comment.
Probe treats crashes as forwarded
Medium Severity
probe_forwarded_keys counts any exception as evidence that a key was forwarded. SAMPLE_VALUES["tool_choice"] is the object that makes ModelSettings raise, so the OpenAI Agents probe reports the key as forwarded and passes while the handler crashes.
Reviewed by Cursor Bugbot for commit 172a7ac. Configure here.


Every time I ask "how would I use the turnkey SDK here?", I hear about how model params aren't currently being passed to models. This PR fixes that.
Not entirely sure about the implementation though: we ended up definiting the list of params supported by each of the underlying SDKs, because I couldn't find a better export from those packages, and iterating on types felt harder than the explicit list. The downside is that if/when things change, we'd need to update the code. But presumably we already need to do that for our model parameter schemas in Gonfalon.
Model parameters set in an AI Config were silently ignored by most handlers. Every handler now passes them through to the provider.
extra_*,model_kwargs), remote MCP servers, the Claude Agent SDK's host-process settings (cli_path,env,cwd,add_dirs,permission_mode, sessions, hooks, callbacks), and keys that set data location or retention, server-side state, identity, runtime wiring or API shape, extra prompt content, or safety configuration (for examplestore,truncation,metadata,user,include,system,guardrails).invokeandstreamforward the same keys, and so do each handler and its native graph.thinking,reasoning,text, ...) is dropped when the config sets it to anything else.packages/and fails if one has no forwarded-keys list, so a new handler package must declare one.max_turns,top_p), the providers' own names and what the UI writes.🤖 Generated with Claude Code
Note
Overview
AI Config
model.parametersare now applied on provider calls instead of being ignored. A sharedmodel_parametersreader andselect_forwarded_parametersfilter sit in the client package; each handler merges only its hand-maintained forwarded key set (cross-SDK TESTING.md 1.12) intoClaudeAgentOptions, Anthropic Messages, LangChain chat constructors, or LiteLLMModelSettings/Runner.Security and consistency: Credentials, endpoints,
extra_*/model_kwargs, stream wiring, and Claude Agents host controls (cli_path,env,cwd, permissions, sessions, etc.) are explicitly excluded and dropped. Handler-owned fields (model,messages,system, tools) cannot be overridden from parameters. UI keys the SDK does not accept are dropped withoutTypeError; object-typed params with wrong shapes are dropped. Claude Messages maps top-leveleffortintooutput_config; LangChain applies alias renames (max_tokens_to_sample,stop); LiteLLM tightensreasoning/tool_choiceand stops reading camelCase aliases.Coverage: Native graph paths use the same forwarding as invoke/stream (e.g. root
max_turnsfor LiteLLM graphs). Large drift/probe/never_forwardedtest suites lock the allowlists to each SDK’s surface.Reviewed by Cursor Bugbot for commit 172a7ac. Bugbot is set up for automated code reviews on this repo. Configure here.