TanStack AI version
0.45.1 (@tanstack/openai-base@0.9.14; behavior unchanged on current main)
Framework/Library version
Tanstack Start
Describe the bug and the steps to reproduce it
Package: @tanstack/openai-base
Summary
When the input schema of a tool cannot be rewritten into OpenAI's strict Structured Outputs subset, the tool converters send the tool with strict: false and the schema as-authored. That fallback is the right call (the alternative is the API rejecting the entire request). However, I believe exercising this fallback should be made clear to devs. currently the converters have no logger access and emit nothing. The developer keeps a working tool and loses strict without any signal that it happened.
What triggers the fallback today
isStrictModeCompatible sends a tool non-strict when the schema contains any of:
oneOf, allOf, not, $ref, $defs, definitions anywhere in the tree (note that schema converters routinely emit $ref/definitions for reused sub-schemas, so ordinary zod-derived schemas can hit this),
- a typeless schema node, e.g. the
{} that z.any() / z.unknown() produce, or a bare boolean schema,
- an open object (
additionalProperties not false, or no properties), e.g. z.record(),
- an optional field inside an
anyOf variant (untrackable null widening).
A single z.unknown() field anywhere de-stricts the whole tool. The source comments note MCP servers (e.g. Notion) routinely emit these, so the whole MCP tool category runs non-strict today. The list may also grow (e.g. tuple forms, see #1208 and #1210), which makes discoverability worse over time.
Why developers cannot find this out on their own
The only way to observe the downgrade is to capture the actual wire request (injected fetch, proxy, provider request logs) and notice "strict": false on the tool definition. Nothing in the library's logs, types, return values, or docs says it happened. The observable symptom is just a slightly higher rate of malformed tool arguments, which is easy to misattribute to the model or the prompt. A developer who chose their provider and schema specifically to get guaranteed-valid arguments has no way to learn that the guarantee is not in effect.
For comparison, both official OpenAI SDKs make this state visible in their own ways: openai-node's strict transform throws ("cannot be represented in strict Structured Outputs"), and openai-python rewrites the schema into the strict subset. TanStack's keep-the-tool-callable behavior is friendlier than either, but it is the only one of the three that says nothing.
Suggested fix
Log once per tool when the fallback triggers, naming the tool and the specific reason (which keyword or condition failed the gate), e.g.:
tool "lookup_weather" sent with strict: false: schema uses $ref, which cannot be represented in strict Structured Outputs
The converters are pure functions today; the adapters already carry a logger channel, so the plumbing exists one level up.
The level is a genuine judgment call and I would leave it to maintainers: warn because the developer probably believes they have a guarantee they do not have, and it is actionable (restructure the schema and strict mode comes back); info because the tool still works as documented; debug because it is wire-level detail. My own preference is warn, at most once per tool per process, but any level is a large improvement over silence.
Happy to PR this.
Terms & Code of Conduct
TanStack AI version
0.45.1 (
@tanstack/openai-base@0.9.14; behavior unchanged on current main)Framework/Library version
Tanstack Start
Describe the bug and the steps to reproduce it
Package:
@tanstack/openai-baseSummary
When the input schema of a tool cannot be rewritten into OpenAI's strict Structured Outputs subset, the tool converters send the tool with
strict: falseand the schema as-authored. That fallback is the right call (the alternative is the API rejecting the entire request). However, I believe exercising this fallback should be made clear to devs. currently the converters have no logger access and emit nothing. The developer keeps a working tool and losesstrictwithout any signal that it happened.What triggers the fallback today
isStrictModeCompatiblesends a tool non-strict when the schema contains any of:oneOf,allOf,not,$ref,$defs,definitionsanywhere in the tree (note that schema converters routinely emit$ref/definitionsfor reused sub-schemas, so ordinary zod-derived schemas can hit this),{}thatz.any()/z.unknown()produce, or a bare boolean schema,additionalPropertiesnotfalse, or noproperties), e.g.z.record(),anyOfvariant (untrackable null widening).A single
z.unknown()field anywhere de-stricts the whole tool. The source comments note MCP servers (e.g. Notion) routinely emit these, so the whole MCP tool category runs non-strict today. The list may also grow (e.g. tuple forms, see #1208 and #1210), which makes discoverability worse over time.Why developers cannot find this out on their own
The only way to observe the downgrade is to capture the actual wire request (injected fetch, proxy, provider request logs) and notice
"strict": falseon the tool definition. Nothing in the library's logs, types, return values, or docs says it happened. The observable symptom is just a slightly higher rate of malformed tool arguments, which is easy to misattribute to the model or the prompt. A developer who chose their provider and schema specifically to get guaranteed-valid arguments has no way to learn that the guarantee is not in effect.For comparison, both official OpenAI SDKs make this state visible in their own ways: openai-node's strict transform throws ("cannot be represented in strict Structured Outputs"), and openai-python rewrites the schema into the strict subset. TanStack's keep-the-tool-callable behavior is friendlier than either, but it is the only one of the three that says nothing.
Suggested fix
Log once per tool when the fallback triggers, naming the tool and the specific reason (which keyword or condition failed the gate), e.g.:
tool "lookup_weather" sent with strict: false: schema uses $ref, which cannot be represented in strict Structured Outputs
The converters are pure functions today; the adapters already carry a
loggerchannel, so the plumbing exists one level up.The level is a genuine judgment call and I would leave it to maintainers:
warnbecause the developer probably believes they have a guarantee they do not have, and it is actionable (restructure the schema and strict mode comes back);infobecause the tool still works as documented;debugbecause it is wire-level detail. My own preference iswarn, at most once per tool per process, but any level is a large improvement over silence.Happy to PR this.
Terms & Code of Conduct