You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A tool parameter typed as a Pydantic discriminated union (Annotated[A | B, Field(discriminator="...")], with or without | None) produces an Ollama tool schema of {"type": "string"}. The \$defs block is retained but unreferenced, and any dict payload is rejected by validate_tool_arguments.
Reproducer
fromtypingimportAnnotated, LiteralfrompydanticimportBaseModel, Fieldfrommellea.backends.toolsimport (
MelleaTool,
convert_function_to_ollama_tool,
validate_tool_arguments,
)
classCat(BaseModel):
kind: Literal["cat"]
name: strclassDog(BaseModel):
kind: Literal["dog"]
name: strbreed: strdefact(pet: Annotated[Cat|Dog, Field(discriminator="kind")]) ->str:
"""Act on a pet. Args: pet: the pet """return"ok"tool=convert_function_to_ollama_tool(act)
print(tool.function.parameters.model_dump(exclude_none=True)["properties"])
# {'pet': {'type': 'string', 'description': 'the pet'}}mt=MelleaTool.from_callable(act)
validate_tool_arguments(
mt,
{"pet": {"kind": "dog", "name": "Rex", "breed": "lab"}},
strict=True,
)
# ValidationError: Input should be a valid string
Expected behaviour
The pet schema should preserve the union structure (the two object schemas plus the discriminator) so an LLM can emit a valid payload, and validate_tool_arguments should accept a correctly-shaped dict and reject an incorrectly-shaped one.
Both the \$ref-inlining pass and _is_complex_anyof in mellea/backends/tools.py only inspect \$ref and properties on the anyOf sub-schemas. Neither descends into oneOf, so the parameter falls through to the flat {"type": "string"} fallback and the \$defs block is discarded for this property.
This is a structurally similar gap to the allOf case discussed on PR #896 and overlaps with the recursive-resolution work needed for #911 — all three are variants of "unresolved structure one level deeper than the inliner looks".
Scope / impact
Affected use cases: command-pattern tools (op: Annotated[CreateUser | DeleteUser | UpdateUser, Field(discriminator="action")]), polymorphic message/event parameters, and any tool signature that mirrors an existing discriminated-union domain model.
Severity: medium. Not a crash or data-loss bug, but makes an idiomatic Pydantic pattern functionally unusable as a tool parameter.
Silent failure: no error at MelleaTool.from_callable(...) or schema generation. Only surfaces as validation failures at tool-call time or as LLM hallucination when the model sees {"type": "string"} and has to guess the payload.
Problem
A tool parameter typed as a Pydantic discriminated union (
Annotated[A | B, Field(discriminator="...")], with or without| None) produces an Ollama tool schema of{"type": "string"}. The\$defsblock is retained but unreferenced, and any dict payload is rejected byvalidate_tool_arguments.Reproducer
Expected behaviour
The
petschema should preserve the union structure (the two object schemas plus the discriminator) so an LLM can emit a valid payload, andvalidate_tool_argumentsshould accept a correctly-shaped dict and reject an incorrectly-shaped one.Actual behaviour
{"pet": {"kind": "dog", "name": "Rex", "breed": "lab"}}Input should be a valid string){"pet": "just a string"}{"pet": {"name": "Rex"}}(missing discriminator)not a string)Root cause
Pydantic emits the discriminated union as:
{ "anyOf": [ {"discriminator": {...}, "oneOf": [{"\$ref": "..."}, {"\$ref": "..."}]}, {"type": "null"} ] }Both the
\$ref-inlining pass and_is_complex_anyofinmellea/backends/tools.pyonly inspect\$refandpropertieson theanyOfsub-schemas. Neither descends intooneOf, so the parameter falls through to the flat{"type": "string"}fallback and the\$defsblock is discarded for this property.This is a structurally similar gap to the
allOfcase discussed on PR #896 and overlaps with the recursive-resolution work needed for #911 — all three are variants of "unresolved structure one level deeper than the inliner looks".Scope / impact
op: Annotated[CreateUser | DeleteUser | UpdateUser, Field(discriminator="action")]), polymorphic message/event parameters, and any tool signature that mirrors an existing discriminated-union domain model.MelleaTool.from_callable(...)or schema generation. Only surfaces as validation failures at tool-call time or as LLM hallucination when the model sees{"type": "string"}and has to guess the payload.{"type": "string"}. fix: tool call arguments #896 does not expand the detection to coveroneOf.Environment
mainplus fix: tool call arguments #896Related
allOfvariant was discussed there and deferred)\$refresolution — likely shares the fix)