* fix(openai): surface gateway errors reported inside an HTTP 200
OpenAI-compatible gateways commit `200 OK` as soon as the upstream provider
accepts a request, so a later provider failure arrives in the body as an
`error` object with no `choices`. That reached the SDK's parse helper and
surfaced as `TypeError: 'NoneType' object is not iterable`, naming neither the
provider, the status, nor the fact that a timeout happened.
The four non-streaming paths now inspect the raw body before parsing and raise
the exception the upstream code maps to, so a masked 504 is catchable exactly
like an honest one. Streaming already had this guard inside the SDK.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(openai): teach the tool-cache fake about with_raw_response
The provider now reads the raw body before parsing, so a client double that
only implements `create` no longer satisfies it. Same shape as the fixes to the
reasoning-effort retry and Snowflake doubles.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(tracing): reset the TraceCollectionListener singleton between tests
TraceCollectionListener caches a TraceBatchManager on the class and
`_initialized` short-circuits `__init__`, so batch state survives for the whole
xdist worker. `test_nested_agent_executor_flow_does_not_finalize_parent_batch`
left `trace_batch_id="debug-trace-batch"` behind, which moved every later trace
POST from /tracing/ephemeral/batches to /tracing/batches/<id>/events. The
recorded cassette then stopped matching, the agent retried, and the second call
found the cassette consumed -- surfacing as ConnectionError in an unrelated
test hundreds of tests later.
Reproduced deterministically by running the leaking test followed by
tests/tracing/test_trace_enable_disable.py::test_trace_calls_when_enabled_via_env;
fails on a68b5e903 too, so this predates the gateway fix it was blocking.
An autouse fixture now clears the cached instance after each test. Two canaries
pin the invariant and fail without the fixture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(tracing): drop the unwritable _listeners_setup canary
Both review bots flagged that the canary read `_listeners_setup` off the class,
where it is always False, so it could never fail. Correct, and the suggested fix
does not work either: `BaseEventListener.__init__` calls `setup_listeners`
(base_event_listener.py:16), which sets the flag on the instance
(trace_listener.py:229), so reading it back through `TraceCollectionListener()`
is always True. Neither read observes a leak, so the canary is deleted rather
than replaced, with the reasoning recorded so it is not re-added.
The same finding showed the fixture was resetting two class attributes that are
never assigned at class level. Only dropping `_instance` is load-bearing, so the
fixture is now one line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(tracing): correct why the _listeners_setup canary is unwritable
setup_listeners returns early when tracing is off and no override applies
(trace_listener.py:213-220), assigning the flag at :229 only when it actually
registers. Construction therefore does not always set it, as the previous note
claimed: with tracing disabled the flag never even reaches the instance dict.
The instance read reports ambient tracing state rather than isolation, which is
a better reason not to assert on it than the one recorded before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(tests): clear trace batch state in place instead of dropping the singleton
Dropping `TraceCollectionListener._instance` made the next construction re-run
`setup_listeners`, re-registering its handlers on the event bus. That broke
tests/telemetry/test_task_failure_instrumentation.py, which requires exactly one
handler per event: the re-registered `on_task_failed` made two. Verified against
a53ecc17f, where the same sequence passes -- the regression was mine.
The leak that needed fixing was batch state, not registration, so the fixture
now clears the manager's batch fields in place. Handler cleanup already belongs
to `cleanup_event_handlers`, and `first_time_handler` keeps its reference to the
same manager object.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(tests): clear tracing context vars so the listener can re-register
Bugbot flagged that keeping the singleton leaves `_listeners_setup` set, so after
`cleanup_event_handlers` wipes the bus `setup_listeners` returns early
(trace_listener.py:208) and tracing silently registers nothing for the rest of
the worker. Confirmed: after a tracing-enabled run, re-running setup restores 0
of 119 handler entries.
Dropping the singleton fixes that but previously broke
test_task_failure_instrumentation. The real cause was a third leak: the
`_tracing_enabled` context var stayed set, so the replacement listener still
believed tracing was on and re-registered `on_task_failed` next to telemetry's.
Clearing the context vars is what makes replacing the listener safe, so the
fixture now does both, and a canary pins it (fails with `assert True is False`
without the drop).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(bedrock): preserve streaming tool call arguments at contentBlockStop
Streaming Converse handlers accumulate tool input as JSON string deltas in
accumulated_tool_input but never fold it back into current_tool_use["input"],
so function_args reads an empty {} at contentBlockStop. Parse the accumulated
input into the tool-use block (with a {} fallback) in both the sync and async
streaming handlers. This is the streaming counterpart of the non-streaming fix
in #5415 (issue #4972).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(bedrock): coerce non-dict streaming tool input to empty dict
json.loads on the accumulated tool input can return a valid-but-non-object
JSON value (e.g. a string or list), which would fail at fn(**function_args)
with a TypeError. Enforce a dict shape before use in both the sync and async
streaming handlers, and add a regression test for the non-dict case.
Addresses CodeRabbit review feedback on #6150.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix: append trailing user turn in native Gemini provider
GeminiCompletion._format_messages_for_gemini maps assistant messages
to Gemini's 'model' role but never guards against the resulting
contents list ending on a model turn. CrewAI's own agent loop (max
iterations, guardrail retries) can produce exactly that history, and
Gemini's generateContent API rejects it with 400 'Requests ending
with a model turn are not supported'.
Mirrors the existing Mistral/Ollama guard in
LLM._format_messages_for_provider, which never applies to Gemini
since gemini/google model strings resolve to this native provider
instead of the LiteLLM fallback path.
Fixes#6972
* fix: append trailing user turn for Gemini on the LiteLLM fallback path
LLM._format_messages_for_provider already guards Mistral/Ollama
against a trailing assistant turn, but Gemini models routed through
the LiteLLM fallback (no google-genai installed, or a model name not
recognized as native) had no equivalent guard. litellm's own
Vertex/Gemini transformation doesn't handle this either, so the
request reaches Gemini's generateContent API unguarded and 400s.
Complements the native-provider fix in GeminiCompletion, covering
both dispatch paths.
* fix: don't append text turn after unresolved Gemini function call
Address CodeRabbit review on #6973: appending a plain 'Please
continue.' user turn after a trailing model turn that contains an
unresolved function_call violates Gemini's function-calling protocol
-- it requires a matching functionResponse, not free text. Raise a
targeted error instead so the caller notices rather than silently
sending a malformed follow-up.
Also strengthens the native-provider formatting tests to assert exact
role sequence and text content (not just the last role), per review,
and adds a regression test for the unresolved-function-call case.
* fix: guard None parts when checking Gemini history for unresolved function call
contents[-1].parts is typed list[Part] | None; iterating it directly
failed mypy (union-attr) on 3.10-3.13. Narrow to [] before the any()
check and document the ValueError in the docstring.
---------
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(llms): normalize scheme and port in Ollama base URL
OLLAMA_HOST follows Ollama's own convention and may be a bare host
("0.0.0.0") or a host:port pair ("127.0.0.1:11434") rather than a full
URL. _normalize_ollama_base_url only appended "/v1", so those values
produced invalid base URLs such as "0.0.0.0/v1", and every request
failed with the misleading error "Failed to connect to OpenAI API:
Connection error." - confusing, since no OpenAI model was requested.
Fill in the missing parts the way Ollama's own client does: prepend
http:// when no scheme is present, append the default port 11434 when
none is present and the scheme is http (https implies 443), then append
the /v1 suffix the OpenAI-compatible endpoint requires.
Six of nine realistic OLLAMA_HOST forms were affected, including
127.0.0.1:11434, which is Ollama's documented default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(llms): strip only the parsed path when normalizing Ollama base URL
Stripping trailing slashes from the whole URL before parsing corrupted
inputs that carry a query or fragment. "http://ollama/?tenant=acme" kept
a "/" path and produced a doubled "//v1", and a query or fragment ending
in "/" silently lost that character.
Parse first, then rstrip only parts.path. Adds regression tests for a
root path alongside a query and for a query value ending in "/".
Reported by CodeRabbit on #7206.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(llms): let current claude models use native structured outputs
NATIVE_STRUCTURED_OUTPUT_MODELS only listed 4.5-era prefixes, so Opus 5,
Sonnet 5, Fable 5 and Opus 4.8 fell through to the forced-tool-call
fallback. That path also overwrites params["tools"], so a call combining
tools with a response_model silently lost the caller's tools.
_infer_provider_from_model documented a pattern-matching fallback it never
performed, so a Claude release newer than the constants list resolved to
"openai". Bedrock ('.' in model) and Azure (every OpenAI prefix) are left
out of that fallback because they would capture gpt-* models.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(llms): route bedrock-namespaced anthropic ids to bedrock
"anthropic.claude-*" is Bedrock's namespace, not the Anthropic API, and it
satisfies the anthropic prefix pattern. Settle it before the pattern loop so
an unlisted Bedrock id picks BedrockCompletion. The region-prefixed form
("us.anthropic.claude-*") was resolving to openai, so this repairs that too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(deps): raise snowflake-connector-python floor for CVE-2026-15925
GHSA-5cc2-282f-jjq2 (CRITICAL): the connector does not verify TLS hostnames,
so a network attacker can impersonate the Snowflake endpoint. Fixed in 4.7.1.
crewai-tools[snowflake] declares "snowflake-connector-python>=3.12.4", which
the lock had resolved to 4.6.0. Following the existing convention, the security
floor goes in [tool.uv] override-dependencies rather than the source
declaration, matching how cryptography is handled.
Relocking also refreshes numpy/humanfriendly/nvidia environment markers, which
re-resolution under the relative exclude-newer window produces regardless of
this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(llms): map default Claude Sonnet 4.6 to its 1M context window
Native AnthropicCompletion fell back to 200k for claude-sonnet-4-6, so the default instance understated the documented limit.
* fix(llms): align Anthropic context windows with active Claude models
Retired Claude 3/2/Instant IDs no longer have dedicated entries; the map now covers the current Claude API lineup and their documented 1M vs 200k windows.
* fix(llms): map Claude Mythos 5 to its documented 1M context window
Native AnthropicCompletion fell back to 200k for claude-mythos-5 even though Anthropic lists a 1M-token window.
* fix(llms): raise Anthropic default max_tokens so large tool calls survive
* fix(llms): default Anthropic to sonnet-4-6 and drop retired models
---------
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
* fix(core): map GPT-5.6 to the official 1.05M context window
LiteLLM fallback treated Sol, Terra, Luna, and the gpt-5.6 alias as unknown and used the 8k default.
* fix(openai): give GPT-5.6 its own 1.05M window
Native lookup matched gpt-5 first, so Sol, Terra, and Luna inherited 1,047,576. Longest-prefix matching keeps gpt-5 and gpt-5.4-mini on their own sizes.
* fix(azure): map GPT-5.6 deployments to the official 1.05M window
Azure had no gpt-5 / gpt-5.6 entry, so Sol, Terra, and Luna fell back to 8k.
* feat(cli): list GPT-5.6 Sol, Terra, and Luna in curated catalogs
The family is generally available; keep gpt-5.5 as the offline default.
* refactor: keep context-window tables in longest-prefix order
Drop the runtime sort and document that new keys must be inserted longest-first so startswith matching stays correct.
* fix(core): resolve prefixed LiteLLM models to the GPT-5.6 window
openai/gpt-5.6-luna kept its provider prefix on self.model, so startswith matching missed the 1.05M mapping. Strip recognized prefixes for lookup and leave unknown ones intact.
* fix(tools): pin SSRF checks to each redirect hop and peer IP
validate_url only inspected the original URL string, so scraping fetches
could follow a 302 to an internal address or rebind DNS between check and
connect. Route safe_get through an HTTPAdapter that re-validates every hop
and connects to the authorised sockaddr, and let FORCE_SAFE_PATHS ignore a
tenant-supplied escape hatch on managed workers.
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
* Potential fix for pull request finding 'Except block handles 'BaseException''
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* test(azure): use a plain stand-in for Responses API delegate mocks
MagicMock instances are not reliably stored on Pydantic PrivateAttr via
BaseLLM.__setattr__, which left _responses_delegate as None and failed
last_response_id / reset_chain assertions on CI.
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* fix(agent): recognize OpenAI Responses API tool-call shape in native tool loop
is_tool_call_list() and extract_tool_call_info() only recognized
Chat-Completions-style ({"function": {...}}), Anthropic-style
({"name", "input"}), and Gemini-style tool-call shapes. The Responses
API's function_call output items are flat dicts shaped
{"id", "name", "arguments"} with no nested "function" key and no
"input" key, so they matched none of the checks.
This caused is_tool_call_list() to misclassify a genuine tool call as
a plain text answer, so the native tool loop returned the raw
tool-call list as the agent's final output instead of executing the
tool. Even after recognizing the shape, extract_tool_call_info() would
have passed an empty arguments dict, since it only read "input" for
the dict fallback.
Verified against LLM(api="responses") with tools attached: the agent
now correctly executes the tool with the parsed arguments instead of
returning the unexecuted tool-call JSON as its answer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test(agent_utils): cover OpenAI Responses API tool-call shape
Regression tests for is_tool_call_list() and extract_tool_call_info()
against the Responses API's flat {"id", "name", "arguments"} dict
shape, alongside existing Chat-Completions and Bedrock/Anthropic
shapes to confirm no regression there.
Confirmed these tests fail against the pre-fix version of
agent_utils.py (3 failures matching exactly the Responses API cases)
and pass against the fix in 37087b7e1.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(llms/openai): convert Chat-Completions tool messages to Responses API input items
_prepare_responses_params() passed non-system messages straight through
as the Responses API "input" array without converting them. That's
fine for plain user/assistant text (matches the API's lenient "easy
input message" shape), but Chat-Completions-style assistant messages
carrying "tool_calls" and "tool"-role messages have no equivalent
shape in the Responses API - it expects standalone "function_call" and
"function_call_output" input items instead. Sending the raw
Chat-Completions shapes gets rejected with a 400 (union-type
validation failure against every Responses API input item variant).
This broke every multi-turn tool-calling conversation over
api="responses" that doesn't rely on auto_chain/previous_response_id
(i.e. the common case: resending full history each turn instead of
referencing server-side state).
Added _convert_message_to_responses_input_items() to translate:
- assistant + tool_calls -> one function_call item per call
- tool role -> function_call_output item
- everything else -> passed through unchanged
Verified against a real multi-turn tool-calling run: the agent now
completes the full conversation and returns the actual extracted
answer instead of erroring on the second turn.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(llms/openai): fix mypy list-item type error in message conversion
_convert_message_to_responses_input_items() was annotated to return
list[dict[str, Any]], but the passthrough branch returns the LLMMessage
argument unchanged. Lists are invariant in mypy, so a bare LLMMessage
(TypedDict) isn't assignable into a list[dict[str, Any]] return - this
was flagged by CI's type-checker job across all Python versions.
Widened the return type (and the local list built in the tool_calls
branch) to list[dict[str, Any] | LLMMessage], matching what the
function actually returns.
Confirmed with a local mypy run and the full openai/agent_utils test
suites (176 passed).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: harden Responses API tool call conversion
* style: format Responses API conversion
* fix: upgrade nltk to 3.10.0 to resolve path traversal vulnerabilities
Upgrades nltk from 3.9.4 to 3.10.0 which fixes three path traversal
vulnerabilities (GHSA-qvv7-cg9c-w4x3, GHSA-fg7f-2386-8897,
GHSA-xh95-f55m-82fw) that were causing the pip-audit CI job to fail.
Also removes the now-obsolete PYSEC-2026-597 ignore entry from the
vulnerability-scan workflow since the vulnerability is fixed in 3.10.0.
* fix: bump aiohttp to >=3.14.2 and cryptography to >=50.0.0 to fix pip-audit vulns
Co-authored-by: theCyberTech <84775494+theCyberTech@users.noreply.github.com>
* fix: default empty Responses tool-call arguments to {}
Missing or empty Chat Completions tool-call arguments were forwarded
as an empty string, which is invalid JSON for Responses API
function_call items and breaks parse_tool_call_args. Normalize to
"{}" and cover the case in regression tests. Also assert a supplied
tool-call id is preserved as call_id.
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
* Removing fallback from call_id
* Removing fallback from call_id
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
Co-authored-by: ViditOstwal <viditostwal@gmail.com>
* fix: preserve provider on LiteLLM-routed models
LiteLLM construction computed the real provider in `__new__` but never
passed it into init, so `BaseLLM` silently defaulted every shared-path
model to `openai`. Infer the provider from a `provider/model` prefix
when none is supplied so groq, cohere, mistral, and the rest report
themselves correctly to callers like the policy engine.
* fix: avoid double-prefixing instructor model strings
With LiteLLM models now carrying a real `provider` while `model` keeps
its `provider/name` form, `InternalInstructor` was building
`groq/groq/...` for `instructor.from_provider`. Skip the prefix when
the model string is already qualified.
* fix: format LiteLLM multimodal content as OpenAI-shaped blocks
Preserving the real provider on the LiteLLM path made
`format_multimodal_content` emit Anthropic-native blocks for
`anthropic/...` models, which LiteLLM rejects. Keep `provider` as the
model identity for policies, but format multimodal blocks with the
OpenAI chat schema when `is_litellm` is set. Expose the formatter helper
on `BaseLLM` so native OpenAI/Azure completions share the same API.
* fix: patch the event-bus singleton in usage event tests
`TestEmitCallCompletedEventPassesUsage` mocked `CrewAIEventsBus.emit`
on the class, but `_emit_call_completed_event` calls the
`crewai_event_bus` singleton. Under CI that left the real bus emitting
and the mock never seeing a call. Patch the singleton where
`base_llm` imports it instead.
* fix: harden llm call-failed event test against VCR fallback
`test_llm_emits_call_failed_event` marked VCR while mocking
`_handle_completion`, and its cassette recorded a successful reply.
When the class-level patch missed under xdist, VCR replayed success
and the expected exception never fired. Patch the instance method and
drop the cassette so the failure path cannot silently succeed.
* fix: patch the event-bus singleton instance in emit mocks
`patch.object(CrewAIEventsBus, "emit")` misses callers when an
instance-level `emit` shadows the class method — common under xdist
after other suites touch the singleton. Patch `crewai_event_bus.emit`
on the shared instance instead across the LLM event mock fixtures.
* Fix Anthropic native provider to include cache tokens in input totals.
Anthropic reports cache read and cache creation separately from input_tokens; fold them into input_tokens and total_tokens so billed usage is not underreported on cached workloads.
* Reconcile unreconciled Anthropic cache tokens in UsageMetrics.
LiteLLM and flow event paths can pass raw Anthropic usage where input_tokens excludes cache counters; fold them into prompt and total tokens without double-counting native provider payloads.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document UsageMetrics token field semantics for flows and crews.
Clarify that total_tokens is prompt plus completion, breakdown fields are not additive, and Anthropic cache counters are folded into prompt_tokens.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document Anthropic cache token accounting in LLM provider docs.
Explain how split Anthropic input counters map to UsageMetrics and link to the flows field semantics section.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Restore cache_creation_tokens breakdown in UsageMetrics normalization.
Keep cache writes as a separate breakdown field while they remain folded into prompt_tokens for billed totals.
* Replace cross-page doc links with plain-text section references.
Avoid internal hyperlinks between concept pages for SEO; point readers to section names in prose instead.
* Apply ruff formatting to usage metrics helpers.
* Sync ar, ko, and pt-BR docs for Anthropic usage metrics updates.
Translate UsageMetrics semantics and Anthropic cache token accounting changes in crews, flows, and llms concept pages.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: clear CodeQL incomplete URL substring sanitization alerts
Replace hostname substring checks with urlparse hostname matching in
RAG DataType classification, and assert the full mocked Stagehand
navigate result instead of searching for a URL substring.
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
* test: call DataTypes.from_content in GitHub hostname tests
from_content lives on DataTypes, not the DataType enum.
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
* test: harden tool-call streaming emit mock against instance shadowing
CI failed when class-level CrewAIEventsBus.emit patches were shadowed by
the singleton instance. Patch both the class and crewai_event_bus.emit,
and read events from kwargs/args explicitly.
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
An ordinary agent with a tool fails on the whole GPT-5.6 family:
Agent(role=..., goal=..., backstory=...,
llm=LLM(model="openai/gpt-5.6-sol"), tools=[multiply])
Function tools with reasoning_effort are not supported for gpt-5.6-sol in
/v1/chat/completions. To use function tools, use /v1/responses or set
reasoning_effort to 'none'.
Nothing sets reasoning_effort -- not the user, not CrewAI. The family applies a
server-side default and then refuses it once tools are present. Confirmed with
raw HTTP, no CrewAI involved, on a payload with no reasoning_effort key at all:
gpt-5.6-sol tools, no reasoning_effort key -> 400
gpt-5.6-sol tools, reasoning_effort="none" -> OK
gpt-5.5 tools, no reasoning_effort key -> OK
gpt-5.4 tools, no reasoning_effort key -> OK
gpt-5.2 tools, no reasoning_effort key -> OK
So this is GPT-5.6 only, and it needs an explicit "none" -- dropping the key is
what the rejected request already looked like.
Recovered from the error rather than a model list: catch the 400, resend with
reasoning_effort="none", once. No model names, so a family OpenAI restricts later
works without a release here. Detection matches the structured `param` field plus
the message, so the unrelated "Unsupported value" 400 that o1/o3 return for
"none" isn't mistaken for this one, and the retry can't loop.
Verified against main with real agents (no reasoning_effort anywhere):
model no tools tools tools + reasoning=True
gpt-5.6-sol ok / ok 400 / ok hang / ok
gpt-5.6-terra ok / ok 400 / ok - / ok
gpt-5.6-luna ok / ok 400 / ok - / ok
gpt-5.5 ok ok -
gpt-5.2 ok ok ok / ok
gpt-4o ok ok -
On main, tools + Agent(reasoning=True) produced no output and no error and was
killed at 420s; gpt-5.2 with the same config finishes in ~40s. Both the 400 and
that hang are fixed.
Tests: 15 cases, including agent definitions with tools, with tools plus
reasoning=True, and without tools. tests/llms + tests/agents -> 961 passed
(1 pre-existing unrelated failure from a local OLLAMA_API_KEY env leak).
Ruff + mypy clean.
* fix(openai): make tool calling work on the Responses API path
An agent with tools on api="responses" never produced an answer. It returned the
raw tool-call list instead:
[{'id': 'call_...', 'name': 'multiply', 'arguments': '{"a":17,"b":23}'}]
Three defects in the chain, all on the Responses side only:
1. `is_tool_call_list()` knew the OpenAI-nested, Anthropic, Bedrock and Gemini
shapes but not the Responses one ({"id", "name", "arguments"} -- no nested
"function", no "input"). The list wasn't recognized as tool calls, so the
executor handed it back verbatim as the final answer.
2. `extract_tool_call_info()` read "arguments" only from a nested "function"
object, falling back to "input". For the Responses shape both missed and the
arguments silently became {}, so the tool would have run with no input.
3. With those fixed the tool ran, then the follow-up request 400'd:
Invalid type for 'input[1].content': expected one of an array of objects
or string, but got null instead.
Tool calling is expressed differently by the two APIs. Chat Completions uses an
assistant message carrying `tool_calls` with content: None, then role: "tool"
results. The Responses API uses flat function_call / function_call_output items
keyed by call_id. Those messages were passed through untranslated.
`_to_responses_input()` now converts them. Messages without tool calls pass
through unchanged, so nothing else moves.
Verified end to end against the live API:
api="responses" + tools -> 391 (was raw tool-call JSON)
chained multi-step tool calls -> 400 (17*23, then +9)
completions path (control) -> 391 (unchanged)
The generated `input` payload was also posted to /v1/responses directly and
accepted, and the pre-fix chat-shaped payload confirmed as a 400.
This is why api="responses" never worked for agents: the provider side has had a
full Responses implementation since #4258/c4c9208, but the executor never learned
the shape it emits. Fixing it also unblocks routing gpt-5.4+ tool calls to the
Responses API instead of dropping reasoning_effort.
Tests: 12 cases covering recognition, extraction (including that the Chat
Completions and Bedrock shapes are unaffected), translation of assistant/tool
messages, parallel calls, assistant text alongside tool calls, non-string tool
output, and the full prepared `input` list.
* fix(openai): prefer Responses "call_id" over the item's own "id"
Per CodeRabbit review. A raw Responses function_call item carries both keys with
different values, confirmed against the live API:
keys ['arguments', 'call_id', 'id', 'name', 'status', 'type']
id fc_0adeb715c5d740c7006a65ccb72b948199872ad8b5a5c53108
call_id call_dEoHFrYnOgWYvk17FymdcDZ5
function_call_output must reference call_id. Reading the item's own "id" would
produce a tool result the model can't correlate back to its invocation.
Our own _extract_function_calls_from_response already maps item.call_id into "id",
so the normal path was correct and the existing tests passed. But
extract_tool_call_info is a shared helper reached from every provider's tool loop,
and a raw Responses item is a plausible thing to hand it -- silently picking the
wrong identifier is a bad trap to leave in place for one line of guard.
Tests: raw item extraction asserting call_id is chosen over id, and a round-trip
check that the id extracted from a call is the one sent back with its result.
997 passed across tests/llms, test_agent_utils and tests/agents (1 pre-existing
unrelated failure from a local OLLAMA_API_KEY env leak). Real two-agent chained-tool
run still returns the correct answer.
---------
Co-authored-by: João Moura <joaomdmoura@gmail.com>
The pro tier is not served by /v1/chat/completions. Probing the live endpoints:
model /v1/chat/completions /v1/responses
gpt-5-pro 404 OK
gpt-5.5-pro 404 OK
gpt-5.4-pro 404 OK
gpt-5.2-pro 404 OK
o1-pro 404 OK
o3-pro 404 OK
Since api defaults to "completions", LLM(model="openai/gpt-5-pro") fails with
"Model ... not found", which is misleading -- the model exists, the endpoint is
wrong. OpenAI's own 404 text ("This is not a chat model") doesn't make the fix
obvious either.
These requests now route to the Responses API automatically, which is verified to
work for every model above. An explicit api= setting is always honoured.
Model matching normalizes the configured string first, so "openai/gpt-5-pro" and
"gpt-5-pro-2025-10-06" both resolve to "gpt-5-pro". It's an exact list rather
than a "-pro" substring, so a custom deployment named "gpt-4-pro-custom" isn't
swept up.
The chat-completions 404 handler also gained an actionable message: when the
response says responses-only, or the model is a known pro model, the error names
api="responses" instead of just reporting "not found".
Tests: 29 cases covering name normalization, detection, routing (including that
call() reaches the Responses handler), and both 404 message paths.
* Support legacy OpenAI base URL env var
* Add custom OpenAI-compatible endpoint support
* Refactor OpenAI completion module test to restore original module state
- Added logic to save and restore the original OpenAI completion module during the test to prevent issues with class re-imports affecting subsequent tests.
- Ensured that the test checks for the presence of the module and its attributes only after the module is properly reloaded.
- Improved test reliability by avoiding potential failures due to module state changes across tests.
* addressing comments
* feat(cli): introduce JSON crew project support and TUI enhancements
- Added support for creating and running JSON-defined crew projects, allowing users to scaffold projects with a new `create_json_crew.py` file.
- Implemented a full-screen Textual TUI for crew execution in `crew_run_tui.py`, enhancing user interaction with a two-column layout.
- Updated `run_crew.py` to prioritize JSON crew projects and added daemon mode for running without TUI.
- Introduced interactive pickers in `tui_picker.py` for improved CLI prompts.
- Enhanced validation for JSON crew files in `validate.py` to ensure proper structure and agent definitions.
- Updated `.gitignore` to exclude demo and crewai directories.
* feat: update LLM model references to gpt-5.4-mini
- Changed default LLM model from gpt-4o-mini to gpt-5.4-mini across various files, including CLI options, JSON crew configurations, and agent definitions.
- Enhanced benchmark and human feedback functionalities to utilize the new model.
- Improved user interface elements in the TUI for better interaction and feedback during execution.
- Added support for new skills directory in JSON crew project creation.
* feat(benchmark): add crew-level benchmarking functionality
- Introduced a new `benchmark` command in the CLI for crew-level benchmarking, allowing users to specify agents, models, and timeout settings.
- Implemented `CrewBenchmarkCase` to handle crew-level benchmark cases with inputs and criteria.
- Enhanced the benchmark runner to support progress tracking and detailed reporting of results for multiple models.
- Added tests for loading crew benchmark cases and validating their structure.
- Updated existing benchmark functions to accommodate the new crew-level execution model.
* feat(cli): enhance JSON crew project functionality and TUI improvements
- Added optional agent-level guardrails and advanced options in JSON crew configurations to improve output validation and flexibility.
- Updated the TUI to better handle plan step statuses, including visual indicators for task completion and failure.
- Introduced methods for parsing and managing step observation events, ensuring accurate updates to task statuses during execution.
- Enhanced validation for JSON crew projects, ensuring proper structure and error handling for agent and task definitions.
- Added comprehensive tests for new features and validation logic, ensuring robustness in JSON crew project handling.
* refactor(cli): streamline JSON crew project handling and improve validation
- Refactored JSON crew project loading and validation logic to enhance clarity and maintainability.
- Introduced utility functions for finding JSON crew files, improving code reuse across modules.
- Removed deprecated benchmark functionality and associated tests to simplify the codebase.
- Updated CLI commands to utilize the new JSON project structure, ensuring compatibility with recent changes.
- Enhanced test coverage for JSON crew project features, ensuring robust validation and error handling.
* feat(cli): enhance activity log navigation and focus management
- Added functionality to focus on the activity log when navigating through log entries.
- Implemented refresh logic for the log panel to ensure updates are displayed correctly during navigation.
- Improved keyboard navigation for log entries, allowing users to expand and scroll through logs seamlessly.
- Added tests to verify the correct behavior of log navigation and focus management in the TUI.
* feat(cli): enhance JSON crew project interaction and input handling
- Introduced a new function to enable prompt line editing for better user experience during input prompts.
- Updated the JSON crew project wizards to show interpolation hints for dynamic values, improving user guidance.
- Enhanced the handling of missing input placeholders by prompting users for required values during crew setup.
- Refactored the crew run logic to ensure proper loading and preparation of JSON-defined crews, including runtime input management.
- Added tests to verify the correct behavior of new input handling features and JSON crew project interactions.
* feat(cli): improve crew project input prompts and event handling
- Enhanced the `_prompt_text` function to allow for configurable spacing before prompts, improving user experience during input collection.
- Updated the wizards for agent and task creation to utilize the new prompt configuration, ensuring a more compact and streamlined interaction.
- Introduced new plan step lifecycle events (`PlanStepStartedEvent`, `PlanStepCompletedEvent`) to better track the execution status of plan steps.
- Refactored the step executor to emit these events during the execution of tasks, improving observability and debugging capabilities.
- Added tests to verify the correct behavior of new prompt handling and event emissions during crew project execution.
* fix: refine json-first crew interactions
* fix: prioritize common json crew tools
* fix: make json crew more tools expandable
* fix: show json crew tools by category
* feat(memory): update default embedder to OpenAI text-embedding-3-large and enhance memory compatibility
- Changed the default embedding model for Memory to OpenAI text-embedding-3-large, which uses 3072-dimensional vectors.
- Added warnings regarding compatibility issues with existing local memory stores created with 1536-dimensional embeddings.
- Updated documentation to reflect the new default embedder and its configuration options.
- Enhanced the CLI and codebase to support the new embedding model across various components, ensuring a seamless transition for users.
* fix: address PR review feedback for JSON-first crews
Review blockers:
- Forward trained_agents_file to JSON crews: crewai run -f now exports
CREWAI_TRAINED_AGENTS_FILE for the in-process JSON crew path
- Wizard agent picker: Esc/cancel now reprompts instead of silently
assigning the first agent
- JSON tool resolution hard-fails: unknown tool names, missing custom
tool files, and invalid custom tool modules raise JSONProjectError
with actionable messages instead of warn-and-continue
- Embedding dimension mismatch: LanceDB and Qdrant Edge storages raise
EmbeddingDimensionMismatchError with reset/pin guidance instead of
silently zero-filling vectors or returning empty search results
- Custom tool code execution documented in loader docstring and the
scaffolded project README
CI fixes:
- ruff format across lib/
- All 133 PR-introduced mypy errors fixed (llm.py lazy-litellm and
cli.py lazy command shims now use TYPE_CHECKING imports; textual
is_mounted misuse fixed; pick_many overloads; misc annotations)
Bot review comments:
- Empty except blocks now have explanatory comments or debug logging
- Removed unused _C_BG/_C_PANEL/_C_BORDER globals and redundant
import re; tests use a single import style for create_json_crew
Tests: trained-agents propagation, wizard cancel, tool resolution
failures, and dimension mismatch guidance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: address second round of PR review comments
Cursor Bugbot:
- Wizard agent slugs: strip to [a-z0-9_] and fall back to agent_<n> so
symbol-only roles can't produce an empty agents/.jsonc filename
- Wizard task names: dedupe against prior task names and fall back to
task_<n> for symbol-only descriptions
CodeRabbit:
- Agent.message(): import Task explicitly at runtime instead of relying
on the namespace injection done by crewai/__init__
- Async executor: move the native-tools-unsupported fallback from
_ainvoke_loop_react (self-recursion) to _ainvoke_loop_native_tools,
mirroring the sync implementation
- StepExecutor downgrade: keep the in-step conversation and append the
text-tooling instructions instead of rebuilding messages, so completed
native tool calls are not re-executed
- crewai-files: extension-based MIME lookup now runs before byte
sniffing so csv/xml types are not degraded to text/plain
- Memory storages: validate every record in a save() batch against a
consistent embedding dimension (LanceDB previously checked only the
first record); added mixed-batch tests
- _print_post_tui_summary now typed against CrewRunApp
- Docs: Azure OpenAI default embedder change called out in the memory
migration warning and provider table
Code quality bots:
- Removed unused _C_YELLOW/_C_CYAN (crew_run_tui) and _GREEN (tui_picker)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cli): accordion tool picker in JSON crew wizard
The flat tool list had grown to ~90 rows. The picker now shows:
- Common tools always visible at the top
- Every other category as a single expandable row with tool and
selection counts (e.g. "Search & Research (27 tools, 2 selected)")
- Expanding a category collapses the previously expanded one
- Selections persist across expand/collapse via new preselected
support in pick_many; cursor follows the toggled category row
tui_picker gains preselected + initial_cursor options on pick_many,
and Esc in multi-select now confirms the current selection instead of
discarding it (required so collapsing can't silently drop choices).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(cli): remove --daemon flag from crewai run
The flag only affected JSON crew projects — classic and flow projects
ignored it entirely, which made the behavior inconsistent. Removed the
option, the daemon code path (_run_json_crew_daemon), and its helper
(_load_json_crew_with_inputs).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: update run command tests after --daemon removal
lib/crewai/tests/cli/test_run_crew.py still asserted the old
run_crew(trained_agents_file=..., daemon=False) call signature.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): exit codes, mid-run quit, async statuses, hyphen placeholders
Addresses the latest Bugbot review round:
- Failed JSON crew runs now exit non-zero (SystemExit(1)) so scripts
and CI don't treat failures as success, mirroring the classic path
- Quitting the TUI mid-run now ends the process (os._exit(130));
kickoff runs in a thread worker that cannot be force-cancelled, so
letting the CLI return would leave LLM/tool work burning tokens in
the background
- Sidebar task statuses are now async-safe: completion/failure events
resolve the task's own row via identity instead of assuming the most
recently started task, and starting a task no longer blanket-marks
earlier active rows as done
- The runtime-input prompt regex now accepts hyphenated placeholder
names ({my-topic}), matching kickoff's interpolation pattern
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: validation safety, custom tool sandboxing, TUI log integrity, memory error surfacing
- Deploy validation no longer executes project code: validation mode
checks tool declarations structurally (well-formed entries, custom
tool file exists) without importing or instantiating anything.
custom:<name> resolution only happens on the actual run path.
- custom:<name> is constrained to [A-Za-z_][A-Za-z0-9_]* and the
resolved path must stay inside the project's tools/ directory, so
custom:../foo or absolute-path names cannot execute code outside it.
Tool paths resolve relative to the crew project root, not cwd.
- TUI task logs are built from per-task state captured at task start
(idx, description, agent, start time); an out-of-order completion
takes its output from the event and no longer steals or resets the
current task's streamed steps/output.
- EmbeddingDimensionMismatchError now inherits ValueError instead of
RuntimeError so background saves surface it through
MemorySaveFailedEvent instead of silently dropping the save; the
shutdown catch in _background_encode_batch is narrowed to the
"cannot schedule new futures" case.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): declared project type wins over crew.json presence
A flow project that also contains a crew.json(c) file now runs and
validates as the flow it declares in pyproject.toml instead of being
hijacked by the JSON crew path. Both crewai run (_has_json_crew) and
deploy validation (_is_json_crew) check tool.crewai.type; a missing or
unreadable pyproject still means a bare JSON crew project.
Also documents why StepObservationFailedEvent intentionally marks the
plan step "done": the event signals an observer failure, not a step
failure, and the executor continues past it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): type the declared_type locals so mypy stays clean
Comparing an Any-typed .get() chain returns Any, which tripped
no-any-return on the previous commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(otel): surface real finish_reason + sampling params + response.id on LLM events
Companion to the OTel GenAI emitter compliance work in crewai-enterprise
(CON-172). Today the enterprise emitter reads these fields off the OSS
LLM events via `getattr(..., None)`, so it produces valid (but partial)
spans against the existing OSS surface. This change makes those fields
first-class on the events so spans can carry the real provider data.
What this adds:
- `LLMCallStartedEvent` gains the sampling-param fields the emitter needs
for `gen_ai.request.*`: `temperature`, `top_p`, `max_tokens`, `stream`,
`seed`, `stop_sequences`, `frequency_penalty`, `presence_penalty`, `n`.
All optional; existing call sites keep working.
- `BaseLLM._emit_call_started_event` introspects those values off `self`
(the LLM instance) via `getattr(..., None)` so every provider gets the
fields propagated for free without per-provider plumbing.
- `LLMCallCompletedEvent` gains `finish_reason: str | None` and
`response_id: str | None`. A field validator coerces any non-string
value (MagicMock, unexpected provider object) to None so the event
never raises on construction.
- `LLM._emit_call_completed_event` accepts both as kwargs.
- `LLM` (LiteLLM path) gets a defensive `_extract_finish_reason_and_response_id`
helper that handles both streaming (`StreamingChoices`) and non-streaming
(`Choices`) shapes and is wired into every completion-event emission site.
- Provider completions extract native values from their SDK responses and
pass them through:
- OpenAI: `_extract_responses_finish_reason_and_id` for Responses-API,
`_extract_finish_reason_and_id` for Chat-Completions.
- Anthropic: `_extract_finish_reason_and_id` (Messages API + streaming).
- Bedrock: `_extract_finish_reason_and_id` (`stopReason` from converse).
- Gemini: `_extract_finish_reason_and_id` (`finish_reason` from candidates).
- Azure: inherits via OpenAI sub-class; adds the helper for Azure-specific
response shapes.
- openai_compatible: inherits from OpenAICompletion, no edits needed.
Compatibility:
- All new fields are optional with sensible defaults. No existing call
sites need to change.
- The validator on `LLMCallCompletedEvent` swallows non-string values for
the new fields so legacy mocks / exotic provider types don't blow up
event construction.
- Enterprise side already reads these fields defensively, so OSS and
enterprise can merge independently and cut on the same synchronized
release.
Tested against the full LLM + events + provider test suite — all green;
the 14 pre-existing multimodal failures on main are unrelated and
reproduce without this diff.
* fix(bedrock): propagate finish_reason + response_id on async paths
The original commit covered every provider's sync path and Bedrock's
sync streaming path, but two Bedrock async paths still emitted
LLMCallCompletedEvent without finish_reason/response_id:
- _ahandle_converse: the final fallback emit_call_completed_event call
was missing both fields. Added stop_reason + response_id matching the
other emission sites in the same function.
- _ahandle_streaming_converse: response_id was never seeded from the
initial response object, and stream_finish_reason wasn't propagated
to the structured-output and final-text emissions. Now extracts
response_id up front and threads stream_finish_reason through every
completion event.
Adds a dedicated test file covering the new event fields end-to-end:
- LLMCallCompletedEvent.finish_reason / response_id Pydantic validation
(string accepted, None default, non-string coerced to None).
- LLMCallStartedEvent sampling params (all nine fields accepted, default
to None).
- BaseLLM._emit_call_started_event introspecting sampling params off
self, with explicit kwargs overriding.
- BaseLLM._emit_call_completed_event passing finish_reason/response_id
through to the event.
- LLM._extract_finish_reason_and_response_id across the LiteLLM shapes
(non-streaming response, streaming chunk, dict, missing fields,
non-string values, unexpected input).
* fix(otel): correct streaming finish_reason + bedrock response_id semantics
Two correctness fixes uncovered while landing the OTel finish_reason +
response_id plumbing:
- LiteLLM streaming (sync + async): `stream_options={"include_usage": True}`
causes LiteLLM to emit a final usage-only chunk with `choices=[]`. The
post-loop `_extract_finish_reason_and_response_id(last_chunk)` silently
returned `(None, None)` because the last chunk has no choices, even though
earlier chunks carried `finish_reason="stop"`. Track both fields
incrementally inside the loop (mirroring how OpenAI/Gemini/Azure already
handle their native streams) and use the tracked values for the
LLMCallCompletedEvent emission and the partial-response error path.
- Bedrock Converse: `ResponseMetadata.RequestId` is an AWS infra trace id,
not a model-level response id (semantically different from OpenAI's
`chatcmpl-XXX`). Return None for `response_id` rather than mislead
downstream telemetry consumers. The audit-fix's async propagation chain
still works — None propagates through unchanged.
Adds `test_llm_streaming_finish_reason.py` pinning both the sync and async
LiteLLM streaming paths against the include_usage chunk shape.
* refactor(otel): unify LLM event introspection + drop redundant defensive code
Three cohesion cleanups uncovered during PR review, all behavior-preserving:
- LLM.call / LLM.acall in llm.py now delegate to BaseLLM._emit_call_started_event
instead of constructing LLMCallStartedEvent inline. The base helper already
introspects sampling params off self via getattr; the inline duplication was
accidental, not justified, and a duplication risk if anyone adds a tenth
OTel sampling param later.
- Extracted lib/crewai/llms/_finish_reason_utils.py:extract_choices_finish_reason_and_id
as the shared extractor for the choices-based response shape. OpenAI Chat,
Azure, and LiteLLM all read the same shape (response.id + choices[0].finish_reason)
as both object attrs and dict keys. Providers with genuinely different shapes
- Anthropic (stop_reason), Bedrock (stopReason), Gemini (protobuf enum),
OpenAI Responses (status) - keep their own provider-specific helpers.
- Dropped redundant try/except (AttributeError, TypeError) wrappers around
bare getattr(obj, "field", None) calls across the new extraction helpers.
getattr with a default already suppresses AttributeError, and the inner
isinstance / dict.get / int-coercion ops can't raise TypeError in practice.
Kept the catches that legitimately guard against IndexError (e.g. choices[0]
on an empty list).
Tests: 600 passed, 23 skipped, 14 pre-existing multimodal failures unchanged.
Added 12 parametrized tests for the shared helper covering object + dict
shapes, missing fields, non-string coercion, and never-raises invariants.
* chore(otel): drop dead last_chunk variable from async streaming
The streaming-fix commit (49e5581b5) replaced the post-loop
`_extract_finish_reason_and_response_id(last_chunk)` call with the
incrementally-tracked `stream_finish_reason` / `stream_response_id`,
which removed the only reader of `last_chunk` in
`_ahandle_streaming_response`. The declaration and per-iteration
assignment were left behind — harmless but confusing for future
readers because the sync sibling still legitimately uses `last_chunk`
(for usage and content fallbacks via `_handle_streaming_callbacks`).
The async path inlines its usage extraction directly inside the loop
(`chunk.model_extra.get("usage")`), so there's no fallback consumer.
Drop both lines.
Sync path untouched — `last_chunk` there is still load-bearing.
* fix(otel): coerce non-list stop_sequences to list[str] on LLMCallStartedEvent
Observed in Datadog: gen_ai.request.stop_sequences on a Gemini/Vertex
span surfaced the textproto repr of a google.protobuf.struct_pb2.ListValue
(values { string_value: "\nObservation:" }) instead of a real Sequence[str].
Root cause is upstream - a Vertex AI / Gemini code path stores the stop
list in a protobuf container (RepeatedScalarContainer or ListValue) rather
than a plain Python list. When that container reaches LLMCallStartedEvent
and then BaseLLM._emit_call_started_event hands it to the OTel SDK as a
span attribute, the SDK falls back to str(value) because the type isn't a
recognised Sequence[str] - producing the protobuf textproto string instead
of an array attribute.
* chore: fix ruff lint findings
* refactor(otel): declare sampling params on BaseLLM + honor stop overrides + dict chunk id
* fix: widen max_tokens to int | float | None + apply ruff format
* fix(otel): coerce unknown finish_reason / response_id to None instead of stringifying
* fix(otel): extract Azure stream finish_reason/id before usage-continue
Match the LiteLLM ordering so a finish_reason or response id riding on a
usage-carrying chunk isn't dropped by the early `continue`.
* fix(otel): report effective max_tokens cap + bedrock structured finish_reason
* Handle Snowflake Claude stringified tool calls
* Fix Snowflake tool id type narrowing
* Extract Snowflake tool result text in summaries
* Bump PyJWT for vulnerability scan
---------
Co-authored-by: João Moura <joaomdmoura@gmail.com>
* Fix structured output leaks in tool-calling loops
* addressing comments
* drop scripts
* Update Gemini agent tests to include structured output with thoughts and bump model version to 2.5-flash
* merge
* Update Anthropic test cases to use new model and tool structure
- Changed the model from "claude-3-5-haiku-20241022" to "claude-sonnet-4-6" in the test setup.
- Updated the request and response formats in the YAML test cassette to reflect the new tool structure and improved content formatting.
- Adjusted the expected response body to match the new output format from the assistant, including changes in tool usage and response details.
- Increased rate limit values in the response headers for better testing scenarios.
* adjusted bedrock cassettes
* adjusting cassettes for bedrock
* fix test
* Update VCR configuration to use 'host' instead of 'bedrock_host' for request matching
* feat(azure): forward credential_scopes to Azure AI Inference client
Adds a credential_scopes field to the native Azure AI Inference
provider and a matching AZURE_CREDENTIAL_SCOPES env var
(comma-separated). The value is forwarded to ChatCompletionsClient /
AsyncChatCompletionsClient when set, letting keyless / Entra-based
callers target a specific Azure AD audience (e.g.
https://cognitiveservices.azure.com/.default) without subclassing the
provider. Matches the upstream azure.ai.inference SDK kwarg of the
same name.
Lazy build re-reads the env var so an LLM constructed at module
import (before deployment env vars are set) still picks up scopes —
same pattern as the existing AZURE_API_KEY / AZURE_ENDPOINT lazy
reads. to_config_dict round-trips the field.
* refactor(azure): tighten credential_scopes env handling
Address review feedback:
- Move os.getenv into the helper so AZURE_CREDENTIAL_SCOPES appears once
- Match the surrounding api_key/endpoint `or` style in the validator
- Drop the list() defensive copy in to_config_dict — every other field
in that method (and the base class's `stop`) is assigned by reference
Enables keyless Azure auth (OIDC Workload Identity Federation, Managed
Identity, Azure CLI, env-configured Service Principal) without any
crewAI-specific configuration. Customers whose deployment environment
already sets the standard azure-identity env vars get keyless auth for
free; the existing API-key path is unchanged.
Linear: FAC-40
Add crewai deploy validate to check project structure, dependencies, imports, and env usage before deploy
Run validation automatically in deploy create and deploy push with skip flag support
Return structured findings with stable codes and hints
Add test coverage for validation scenarios
refactor: defer LLM client construction to first use
Move SDK client creation out of model initialization into lazy getters
Add _get_sync_client and _get_async_client across providers
Route all provider calls through lazy getters
Surface credential errors at first real invocation
refactor: standardize provider client access
Align async paths to use _get_async_client
Avoid client construction in lightweight config accessors
Simplify provider lifecycle and improve consistency
test: update suite for new behavior
Update tests for lazy initialization contract
Update CLI tests for validation flow and skip flag
Expand coverage for provider initialization paths
* fix: bump litellm to >=1.83.0 to address CVE-2026-35030
Bump litellm from <=1.82.6 to >=1.83.0 to fix JWT auth bypass via
OIDC cache key collision (CVE-2026-35030). Also widen devtools openai
pin from ~=1.83.0 to >=1.83.0,<3 to resolve the version conflict
(litellm 1.83.0 requires openai>=2.8.0).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve mypy errors from litellm bump
- Remove unused type: ignore[import-untyped] on instructor import
- Remove all unused type: ignore[union-attr] comments (litellm types fixed)
- Add hasattr guard for tool_call.function — new litellm adds
ChatCompletionMessageCustomToolCall to the union which lacks .function
* fix: tighten litellm pin to ~=1.83.0 (patch-only bumps)
>=1.83.0,<2 is too wide — litellm has had breaking changes between
minors. ~=1.83.0 means >=1.83.0,<1.84.0 — gets CVE patches but won't
pull in breaking minor releases.
* ci: bump uv from 0.8.4 to 0.11.3
* fix: resolve mypy errors in openai completion from 2.x type changes
Use isinstance checks with concrete openai response types instead of
string comparisons for proper type narrowing. Update code interpreter
handling for outputs/OutputImage API changes in openai 2.x.
* fix: pre-cache tiktoken encoding before VCR intercepts requests
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alex <alex@crewai.com>
Co-authored-by: Greyson LaLonde <greyson@crewai.com>
GPT-5.x models reject the `stop` parameter at the API level with "Unsupported parameter: 'stop' is not supported with this model". This breaks CrewAI executions when routing through LiteLLM (e.g. via
OpenAI-compatible gateways like Asimov), because the LiteLLM fallback path always includes `stop` in the API request params.
The native OpenAI provider was unaffected because it never sends `stop` to the API — it applies stop words client-side via `_apply_stop_words()`. However, when the request goes through LiteLLM (custom endpoints, proxy gateways),
`stop` is sent as an API parameter and GPT-5.x rejects it.
Additionally, the existing retry logic that catches this error only matched the OpenAI API error format ("Unsupported parameter") but missed
LiteLLM's own pre-validation error format ("does not support parameters"), so the self-healing retry never triggered for LiteLLM-routed calls.
- Delegate supports_function_calling() to parent (handles o1 models via OpenRouter)
- Guard empty env vars in base_url resolution
- Fix misleading comment about model validation rules
- Remove unused MagicMock import
- Use 'is not None' for env var restoration in tests
Co-authored-by: Joao Moura <joao@crewai.com>