* fix(llm_overlay): a role and a key that differ only by surrounding whitespace match
A role that comes from a YAML file often ends in a newline — `role: >`
folds to "Researcher\n" — and a caller writes the key for the clean text,
"Researcher". The two never matched, so the agent kept its declared llm
without a word: the overlay looked active and did nothing.
`llm_overlay(mapping)` now sets a copy of the mapping with the whitespace
around each key dropped, and `overlay_model_for(role)` strips the role
before looking it up; an empty or None role matches nothing. Matching is
otherwise unchanged: exact text, no case folding. The mapping the caller
passed is not touched. The three readers (Agent at construction and after
interpolation, LiteAgent at construction) already go through
overlay_model_for, so they pick this up with no change of their own.
Tests: a key "Researcher" matches "Researcher\n" and " Researcher "; a
key written with a trailing newline matches a clean role; case and inner
whitespace still miss, as do "" and None; the caller's mapping is not
mutated; a YAML-folded template role matches after interpolation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(llm_overlay): two keys that are one role with different models are refused
Review on #7572 (CodeRabbit, iris-clawd): after stripping, "Researcher" and " Researcher " are one key, and the
later entry silently won — the model an agent ran on depended on dictionary order. `_stripped` now refuses a
mapping that names one role twice with different models (ValueError naming the role and both models) and keeps a
harmless duplicate that names the same model once. A test pins both.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* feat(llm): re-resolve llm_overlay after input interpolation rewrites an agent's role
llm_overlay (#7500) resolves an agent's model at construction, by its role text.
A CrewBase crew declares roles as templates in YAML — "Researcher for {repo}" —
that Crew._interpolate_inputs rewrites at kickoff, after construction. An overlay
keyed by the interpolated role, which is the text every trace records, never
matched: on a production flow a plan routed 3 of 5 agents and left the two
templated ones on their declared model.
Agent.interpolate_inputs now looks the overlay up again when the rewrite changed
the role: a key sets llm to the mapped model (create_llm, as construction does),
carrying the streaming flag Crew.kickoff(stream=True) set on the instance it
replaces; a miss, no active overlay, or an unchanged role leaves llm exactly as
it is — construction's resolution and instance stand, nothing reverts. The
executor binds agent.llm per task, after interpolation, so the task runs on the
new model (pinned through prepare_kickoff). Not followed by the swap, documented:
a task's string guardrail LLM, an auto-created Memory LLM, the crew_creation span.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(llm): read llm_overlay once per agent — a re-validation must not replace an llm the agent already runs on
Found while battle-testing the previous commit with real calls: the event bus registers
an agent in its RuntimeState the first time it emits, and RuntimeState(root=[agent])
re-runs Agent.post_init_setup on the same object. Inside a block that maps the agent's
role, the construction-time overlay read (#7500) then replaced the llm the agent was
already running on and dropped the state set on it (stream=True). An agent built outside
the block picked the mapped model up on its second standalone kickoff inside one, against
#7500's own contract; Crew.replay inside a block did the same.
A private flag marks the construction-time read as done; a re-validation keeps the llm the
agent has — the one construction resolved, or the one interpolate_inputs set when the
role changed. Copies (kickoff_for_each) are new instances and read the overlay as before.
Zero-cost tests through RuntimeState; docstrings corrected (a crew Memory built at kickoff
does follow the swap; a stream must be iterated inside the block).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Apply the same contextlib.closing pattern accepted in #7493 to the nine
remaining library-side sqlite3.connect sites: SQLiteFlowPersistence
(init_db, save_state, load_state, save_pending_feedback,
load_pending_feedback, clear_pending_feedback) and SqliteProvider
(checkpoint, prune, from_checkpoint). The connection context manager
only commits or rolls back, so the connections survived in a reference
cycle and kept flow_states.db / checkpoint databases locked on Windows.
Also close the read-back connections in test_checkpoint.py, which made
its TemporaryDirectory cleanup fail on Windows for the same reason.
Add lifecycle and failure-path regression tests.
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
For response_model calls the messages are flattened into one prompt for
InternalInstructor with an f-string, so a multimodal content list reached the
model as its Python repr. AGENTS.md's "Message Content" section says never to
str() the content; use message_content_text() instead.
* Support private app connections
App selectors only accepted UUID connection identifiers, so apps=["github@private"] failed validation.
Accept connection aliases and route them through Clipper like UUID identifiers. This supports private connections and future platform aliases without client releases.
* chore: update tool specifications
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(cli): record why a deployment create failed
`crewai deploy create` counts every attempt (`Create Crew Deployment`) and every
success (`Crew Deployment Created`), but the gap between them carried no cause:
among clients able to emit the success span, the CLI succeeds 96.7% of the time
and the run TUI 36.4%, and nothing said why. A third span, `Crew Deployment
Failed`, now fires for every failure after the attempt is counted, with a closed
vocabulary `reason` (api_4xx, api_5xx, invalid_response, network_error,
zip_error, user_declined, unexpected), the HTTP `status_code` when the API
answered, and the existing `source`. Never the error message.
The request path is factored into `_request_crew_creation`; every exception is
classified, reported and re-raised unchanged, so CLI and TUI behaviour is the
same as before. HTTP failures are classified before `_validate_response`, which
still prints and exits as it did.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(cli): classify deploy create failures by status and by stage
Review fixes on the failure span. Check the HTTP class before the body so a
gateway's HTML page counts as api_4xx / api_5xx with its code. Treat a 2xx
whose body is not a JSON object carrying uuid and status as invalid_response
and exit cleanly, instead of emitting a success span and crashing in the
display step. Recognise archive failures by a dedicated ArchiveError
(a ValueError) raised from create_project_zip, so the git helpers' own
ValueErrors no longer read as zip_error; a failed ZIP write is wrapped and
its partial file removed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(cli): treat staging and temp-file failures as archive errors
`create_project_zip` only wrapped the ZIP write, so an `OSError` while
staging files or creating the temporary archive escaped as a bare
`OSError` and the deploy command recorded it as `unexpected` instead of
`zip_error`. The archive boundary now covers staging, temp-file creation
and the write; the staging directory is removed on every path, and no
partial archive is left behind.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(cli): tell a non-JSON 2xx apart from a non-creation 2xx
A proxy's 200 HTML page and a JSON body missing the creation fields were
both recorded as `invalid_response`. They are different failures, one in
the network path and one in the API contract, so the deploy failure span
now records `invalid_json` for the first and `invalid_creation_response`
for the second. Requested in review.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* feat(tracing): collect human feedback and pause events in the trace
The trace listener subscribed to method and conversation events but not to
the review-gate events or the pause events, so a `@human_feedback` gate
reached the trace only as method_execution_started/finished. A trace could
not say that a run stopped for review, what the reviewer was shown, or what
they answered.
Subscribe to HumanFeedbackRequestedEvent, HumanFeedbackReceivedEvent,
MethodExecutionPausedEvent and FlowPausedEvent through `_handle_action_event`,
as the conversation events are, with the event's own type as the trace type.
Each is a whole-event payload via the default serialization path; no change
to `_build_event_data` or `complex_events`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(tracing): register the gate and pause handlers through _on, keeping the execution-uuid gate
The four new handlers, and the conversation handler this branch had switched by
mistake, registered with event_bus.on and so ran while a kickoff owned an execution
uuid — the case where the OTEL session records these events and the legacy collector
must stay idle. Restored to self._on like every other handler; a test binds an
execution uuid and asserts none of the five are collected into a legacy batch.
Docstrings on the handlers (review bot coverage note).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(tracing): under a tracing kickoff the session records the gate and pause events and the legacy batch stays empty
Two real flows under an in-memory tracing session (the lifecycle tests' pattern): a
@human_feedback gate answered at the console records human_feedback_requested and
human_feedback_received as spans; an async provider that parks the flow records
method_execution_paused and flow_paused. In both the legacy collector, gated by _on,
collects none of the four. Review bot: the uuid-gated test alone would have passed
with the session registrations missing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(memory): close sqlite connections in kickoff task outputs storage
`with sqlite3.connect(...) as conn` only commits or rolls back; it never
closes the connection, which then survives in a reference cycle until a
cyclic GC pass. Every Crew kept an open handle on
latest_kickoff_task_outputs.db, so on Windows the file stayed locked and
any later delete, rename or temp-dir cleanup failed with PermissionError
(WinError 32). Wrap each connection in contextlib.closing, keeping the
existing commit/rollback semantics, and add regression tests.
* test(memory): cover rollback and close on a failed write
---------
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
`_execute_single_native_tool_call` reads the tools handler's cache and
skips the tool body on a hit, but emitted its ToolUsageFinishedEvent
without `from_cache`, so the bus — and every trace built from it — saw a
replayed native function call as a live one. The text-protocol path
(ToolUsage.on_tool_use_finished) already carried the flag. One kwarg.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
A per-run caller sometimes needs specific agents on a different model
without editing the code that builds them. `crewai.llm_overlay` adds a
process-context `role -> model` overlay:
with llm_overlay({"Researcher": "openai/gpt-4o"}):
crew.kickoff()
The overlay is read in the only two places an agent resolves its model from
its role: `Agent.post_init_setup` and `LiteAgent.setup_llm`. When the role is
a key, `create_llm` receives the mapped model instead of the declared `llm`;
otherwise, and outside the block, nothing changes. `create_llm` itself stays
role-blind.
The overlay is a ContextVar, so it follows the calling context and is always
reset on exit. It does not cross plain threads; callers threading agents must
propagate the context with `contextvars.copy_context().run(...)`. The module
docstring says so and a test pins the behaviour.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(files): return None instead of bare raise in get_uploader
get_uploader is documented to return None for an unsupported provider, and
every caller branches on `if uploader is None`. Two fallthrough paths ran a
bare `raise` with no active exception, so an unknown provider and a Bedrock
provider without a configured S3 bucket raised
"RuntimeError: No active exception to reraise" instead of returning None.
Return None in both paths and widen the return types to `... | None`. The
Bedrock "not configured" guard now treats a falsy bucket_name (None or "") as
unconfigured, not only an absent one. The except ImportError re-raises are
unaffected.
Fixes#7282
* fix(files): raise ValueError from get_uploader for unknown/unconfigured providers
Per review, raise a ValueError with a concrete reason instead of returning
None. Returning None let the resolver silently fall back to inline and hid the
misconfiguration from the user, so the docstring no longer promises None and
the return types drop `| None`. The Bedrock guard also treats a falsy
bucket_name (None or "") as unconfigured. The ImportError re-raises are
unchanged.
cleanup skips providers it cannot build an uploader for, so it routes
get_uploader through a local helper that treats the ValueError as
"unavailable" and continues the pass.
* refactor(files): surface get_uploader errors through the resolver
Follow-up to review. get_uploader now raises ValueError, so _get_uploader no
longer promises FileUploader | None: it returns the uploader and lets the error
propagate through resolve() to the caller instead of swallowing it and falling
back to inline. Drop the now-dead `if uploader is None` checks at the two
upload call sites.
Also make the unknown-provider ValueError list the supported providers, and add
a happy-path test that a configured provider returns its uploader.
* fix(files): surface uploader lookup errors in async batch resolution
aresolve_files gathers with return_exceptions=True, which was silently dropping
files when _get_uploader raised (a missing provider SDK, or an unknown or
unconfigured provider). A batch shares one provider, so such a lookup failure
applies to every file: re-raise ValueError and ImportError to surface it,
matching the sync resolve_files path. Genuine per-file upload errors are still
logged and skipped.
* fix(files): only re-raise uploader config errors in async batch resolution
The earlier fix re-raised any ValueError or ImportError from
asyncio.gather(return_exceptions=True), so one unrelated per-file error
(for example a stream that raises ValueError when read) aborted the whole
batch instead of the intended log-and-skip.
_get_uploader now translates the lookup failure into a dedicated
UploaderConfigurationError, and aresolve_files re-raises only that, since
it applies to every file for the provider. Ordinary per-file failures stay
best-effort. Adds regression tests for the wrap, a provider-setup error
surfacing from the batch, and an unrelated per-file error skipped while
the rest resolve.
* style(files): apply ruff import sort and formatting to resolver tests
---------
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
`agent_execution_started` and `agent_execution_completed` are in
`complex_events`, so `_build_event_data` hand-builds their payloads. Those
payloads shipped only agent_role/goal/backstory and dropped two required bus
fields: `AgentExecutionStartedEvent.task_prompt` and
`AgentExecutionCompletedEvent.output`. A trace therefore said which agent ran
but not what it was asked or what it answered.
Add `task_prompt` to the started payload and `output` to the completed one,
whole and untruncated. No new event types, no TraceEvent change.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(flow): support non-primitive types in SQLiteFlowPersistence (#7358)
* test(flow): add docstrings to test classes and step methods
* fix(flow): serialize Decimal and Path as strings in SQLite persistence
* fix(flow): dump BaseModel with mode=python to allow fallback serialization on Any fields
* fix(flow): prioritize model_dump mode=json with fallback to mode=python
* ci: re-trigger test suite
---------
Co-authored-by: Rohit Kanithi <rohitkanithi@users.noreply.github.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* feat: scaffold project with assistant instruction files
- Updated project creation to include `CLAUDE.md` and `GEMINI.md` that import `AGENTS.md`, ensuring consistent guidance across coding assistants.
- Implemented utility functions to copy assistant instruction files during project setup.
- Enhanced documentation in `AGENTS.md` to emphasize the importance of keeping telemetry enabled for optimal performance.
- Added tests to verify the correct scaffolding of assistant instruction files and their contents.
* fix(cli): neutral observability guidance in scaffolded AGENTS.md
- State the observability rule as the user's decision, never a fix for
console warnings, speed, or a "clean" configuration
- Rewrite the AMP section as built-in capabilities: no "free",
"proactively", "sales pitch", or scripted pitches
- Turn the research mandate into a list of sources to consult when
version details matter
- Retarget the scaffold tests to the new wording
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(cli): scaffold assistant files for JSON crews and harden telemetry tests
- create_json_crew, the default `crewai create crew` path, now copies
AGENTS.md, CLAUDE.md and GEMINI.md; AGENTS.md documents the JSON layout
- span helper no longer depends on OTEL_SDK_DISABLED being popped by an
earlier test; thread-scope test stops its worker before leaving the mock
- single import style in the shutdown test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
JsonProvider writes checkpoints with encoding="utf-8" and the runtime
serialises non-ASCII text verbatim, but the checkpoint CLI still opened
them with the platform default encoding. On Windows (cp1252) `crewai
checkpoint info` raised UnicodeDecodeError and `list` showed a 0-byte
entry for any checkpoint containing non-ASCII text. Open the files as
UTF-8 and add regression tests for the three readers.
* fix(cli): overwrite stale poetry.lock backup on windows
os.rename raises FileExistsError on Windows when poetry-old.lock already
exists from a previous run, so a second `crewai update` crashed there
while POSIX silently replaced the file. Use os.replace, which overwrites
on every platform, and add a regression test.
* test(cli): add docstrings to update_crew tests
---------
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
`Memory.read_only` was enforced only on `remember()` and `remember_many()`.
Two other paths still mutated the backing store:
- `update()` re-embedded the supplied content and wrote the record back.
- `recall()` refreshed `last_accessed` through `touch_records()`, so simply
reading a read-only memory left a persistent trace.
Both now respect the flag, so a read-only Memory leaves stored records
unchanged. `update()` returns the existing record untouched rather than
raising, matching the silent no-op behaviour of `remember()`. Explicit
deletion through `forget()`/`reset()` is deliberately unaffected.
Claude-Session: https://claude.ai/code/session_01HkDjVYVzHFEj5re9B8JH9p
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(llms): send reasoning_effort to every openai reasoning model
The completions path gated the parameter behind
is_o1_model = "o1" in model.lower(), a literal substring test. gpt-5, o3 and
o4-mini contain no "o1", so an explicitly configured effort was dropped and the
model thought at the server default. The request still succeeded, so nothing
surfaced -- one measured extraction ran 6.2s with the setting applied against
149.7s with it dropped.
The gate could not be widened: is_o1_model also drives
supports_function_calling, supports_stop_words and the system->user message
rewrite, so marking gpt-5 as an o1 model would report that it cannot call
tools. The parameter is forwarded unconditionally instead, matching the
responses path, and a model that genuinely does not support it says so in a 400
that is retried once without the key.
Also adds "minimal" to LLM.reasoning_effort, which gpt-5 accepts and the
Literal omitted, so the cheapest setting was unreachable on the typed surface.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(llms): gate reasoning_effort on model shape, not every model
Forwarding to every model made a non-reasoning model pay a rejected request
and a retry on every call. `_supports_reasoning_effort` matches on shape
instead -- the o-series, and GPT generation 5 onwards -- so gpt-4o and gpt-4.1
never send the parameter at all.
Matched by shape rather than by a list of names so a new member of an existing
family works without a release here; gpt-6 and o5 already classify correctly.
The unsupported-parameter retry stays as a safety net for the case the shape
match is wrong for a future family, where it costs nothing when the match is
right.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(llms): honour reasoning_effort on compatible servers and fine-tunes
Review follow-ups. A fine-tune (ft:<base>:...) is judged by its base model.
On an OpenAI-compatible server -- anything whose effective base URL is not
api.openai.com, whether set explicitly, via env, or by a provider subclass --
the model name is the server's namespace and says nothing about support, so an
explicit setting is sent as configured; a 400 naming the parameter, in whatever
words the server uses, is recovered by retrying without it, unless it reads as
a complaint about the value. A model that rejected the parameter is remembered
per (endpoint, model) for the process so the rejected call is paid once, and
the drop is logged as a warning since a configured setting is not being
applied. The Literal also gains "xhigh", the remaining value the SDK accepts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(llms): recover reasoning_effort only on evidence the parameter is unknown
Two review follow-ups. The endpoint check now uses the same base URL precedence
as the client itself, so a `client_params` override selects the server. And a
rejection is recovered only when the message says the field is not one the
server knows -- OpenAI's two shapes plus the common compatible-server wordings
("unknown field", "Extra inputs are not permitted") -- rather than any 400 that
lacks a value-sounding word. A pydantic enum complaint such as "Input should be
'low', 'medium' or 'high'" names the parameter but is about its value, and
surfaces instead of being dropped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(llms): remember a reasoning_effort rejection only after the retry succeeds
Two review points on the reasoning_effort fallback. The rejection was recorded
before the retry ran, so a retry that died for an unrelated reason (a dropped
connection, say) silently stopped sending the configured effort for the rest of
the process even though a call without it had never succeeded; the (endpoint,
model) is now remembered only once the retry returns. And _effective_base_url
accepted only a str override in client_params while the SDK, and
_get_client_params, accept httpx.URL too, so a compatible deployment configured
with a URL object was detected as OpenAI and its rejection keyed under the wrong
endpoint; both forms are normalised to the string the client calls.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(cli): reject non-serializable literal_eval results in TUI JSON formatting
_try_parse_structured() accepted any dict/list coming out of
ast.literal_eval(), including values json.dumps() cannot encode such as
[Ellipsis] from a literal [...]. _format_json_in_text() then raised
TypeError: Object of type ellipsis is not JSON serializable, which
propagated through _tick and cancelled the whole crew run.
Validate the parsed object with json.dumps() inside
_try_parse_structured() so only JSON-serializable dict/list values are
returned; anything else falls back to the original text. Fixes#7434.
* fix(cli): contain RecursionError in the TUI JSON formatting boundary
A streamed structure nested deeper than the JSON backend can walk could
raise RecursionError out of _try_parse_structured (from json.loads) or
out of the render-path dumps, escaping _tick and losing the TUI update.
Catch RecursionError when loading, and validate literal_eval results
with the exact kwargs the render path uses, so any structure the render
cannot encode is rejected at the boundary and the raw text renders
instead.
---------
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
mypy does not narrow on `platform.system()`, so Windows-based contributors
get 8 spurious attr-defined/unused-ignore errors from the termios and
resource imports. Switch to `sys.platform` comparisons, which mypy
understands natively, and drop the now-unneeded type-ignore comments.
Fixes#7400
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(agents): request the forced final answer as a user turn
When an agent reaches max_iter, handle_max_iterations_exceeded appended
the "give your best final answer" instruction as an assistant message and
relied on assistant prefill to make the model continue it. Current Claude
models (Opus 5, Sonnet 5, Fable 5.x, the 4.6+ family) reject a request
that ends on an assistant turn with a 400, after the whole iteration
budget has already been spent.
The instruction is now appended as a user turn, which every provider
accepts. The handler's formatted_answer parameter is dropped: every
caller had already appended that text as the last assistant message, so
prefixing it again only duplicated history.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(agents): stop the lite agent loop after the forced final answer
LiteAgent._invoke_loop fell through after handle_max_iterations_exceeded
and issued a regular LLM call on the same history, discarding the forced
answer. Break out of the loop the way CrewAgentExecutor already does, and
assert a single LLM call in the test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* fix(agents): keep null in the task output schema embedded in the prompt
build_task_prompt_with_schema embeds the task output schema into the prompt
via generate_model_description, whose strip_null_types defaults to True.
Combined with ensure_all_properties_required, an Optional[str] = None field
reaches the model as a required, non-nullable string, contradicting the
provider-side response schema generated from the same model.
That sanitizer targets OpenAI strict function-calling schemas. This call site
produces prompt prose, where those constraints do not apply.
Pass strip_null_types=False, matching the existing call for tool schemas in
utilities/agent_utils.py.
Fixes#6774
* test(agents): cover the output_json branch of the prompt schema
build_task_prompt_with_schema embeds a schema on both the output_json and
the output_pydantic branch, and this PR changes both. The regression test
only built a Task with output_pydantic, so the output_json branch shipped
unpinned.
Parameterize over both output attributes. Checked on this branch with the
fix reverted: both cases fail on the missing anyOf, and both pass with it.
Also compare the anyOf members as a set, so member order is not part of the
test contract.
---------
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>
* feat(embeddings): add openrouter provider type definitions
* feat(embeddings): implement OpenRouterProvider
* feat(embeddings): export openrouter provider package symbols
* feat(embeddings): register openrouter in allowed embedding providers
* feat(embeddings): register openrouter provider and overloads in factory
* feat(tools): add openrouter embedding service support
* test(embeddings): add comprehensive openrouter provider and factory tests
* test(embeddings): add openrouter build test in embedding factory
* test(embeddings): add openrouter model key alias and env tests
* test(tools): add openrouter tests for embedding service
* docs: add openrouter embedder configuration example
* docs(ar): sync openrouter embedder translation
* docs(ko): sync openrouter embedder translation
* docs(pt-BR): sync openrouter embedder translation
* feat(embeddings): allow model alias and None fields in OpenRouterProviderConfig
* fix(tools): resolve EMBEDDINGS_OPENROUTER_API_KEY before OPENROUTER_API_KEY
* test(tools): add regression tests for openrouter env var precedence and fallback
* feat(embeddings): drop organization_id and resolve api_key via OPENROUTER_API_KEY only
* fix(tools): use OPENROUTER_API_KEY in embedding service and update tests
* docs: switch openrouter knowledge example to model_name and document OPENROUTER_API_KEY
* fix(tools): default openrouter model to namespaced openai/text-embedding-3-small
---------
Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com>