Skip to content

fix(python): fallback to 'GeneratedModel' when schema has no title#3325

Open
matingathani wants to merge 2 commits into
ComposioHQ:nextfrom
matingathani:fix/json-schema-to-model-none-title
Open

fix(python): fallback to 'GeneratedModel' when schema has no title#3325
matingathani wants to merge 2 commits into
ComposioHQ:nextfrom
matingathani:fix/json-schema-to-model-none-title

Conversation

@matingathani
Copy link
Copy Markdown

Summary

Fixes a crash in json_schema_to_model when a JSON schema has no title field.

Root cause: json_schema_to_model calls create_model(model_name, ...) where model_name = json_schema.get("title"). When title is absent, model_name is None, and Pydantic raises:

TypeError: type.__new__() argument 1 must be str, not None

This hits in practice when LangGraph/LangChain/CrewAI providers wrap tools whose parameter schemas contain anonymous nested objects — for example, array-item schemas that have no explicit title key.

Fix: Fall back to "GeneratedModel" when title is absent, matching the convention already used in schema_converter.py for the same purpose.

Stack trace from the issue:

File ".../composio/utils/shared.py", line 194
    return create_model(model_name, **field_definitions)
                        ^
               model_name is None (should be a string)

Test plan

  • New test: json_schema_to_model with no title creates a valid model (no crash)
  • New test: required fields in a title-less schema remain required
  • Full schema parser test suite passes (pytest tests/test_schema_parser.py — 67 tests)
  • Full unit test suite passes (pytest tests/ — 592 tests)
  • Linter passes (ruff check)

Closes #2435

json_schema_to_model passes model_name directly to pydantic create_model.
When a schema has no 'title' key, model_name is None, causing:
  TypeError: type.__new__() argument 1 must be str, not None

This crashes LangGraph, LangChain, and CrewAI providers whenever they
wrap a tool whose parameter schema contains an anonymous nested object
(e.g. array items that are objects without an explicit title).

Fix: fall back to 'GeneratedModel' when title is absent, matching the
convention already used in schema_converter.py for the same purpose.

Fixes ComposioHQ#2435
Copilot AI review requested due to automatic review settings May 2, 2026 05:54
@matingathani matingathani requested a review from jkomyno as a code owner May 2, 2026 05:54
@vercel
Copy link
Copy Markdown

vercel Bot commented May 2, 2026

@matingathani is attempting to deploy a commit to the Composio Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a crash in the Python schema-to-Pydantic conversion path when a JSON schema object lacks a title, which previously caused pydantic.create_model(None, ...) to raise a TypeError. This improves compatibility with providers (LangGraph/LangChain/CrewAI) that encounter anonymous nested object schemas.

Changes:

  • Add a fallback model name ("GeneratedModel") in json_schema_to_model when title is missing.
  • Add tests covering title-less schemas (no-crash + required-field behavior).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
python/composio/utils/shared.py Prevents create_model from receiving None by adding a fallback model name.
python/tests/test_schema_parser.py Adds regression tests for title-less object schemas and required-field enforcement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread python/tests/test_schema_parser.py
Comment thread python/composio/utils/shared.py Outdated
…name in test

Address Copilot review feedback on PR ComposioHQ#3325:
- Use 'title' in json_schema instead of or-expression to avoid overriding
  an explicit empty-string title (matches schema_converter.py's pattern)
- Assert model_class.__name__ == 'GeneratedModel' in test so the fallback
  name cannot silently regress
matingathani added a commit to matingathani/composio that referenced this pull request May 2, 2026
…name in test

Address Copilot review feedback on PR ComposioHQ#3325:
- Use 'title' in json_schema instead of or-expression to avoid overriding
  an explicit empty-string title (matches schema_converter.py's pattern)
- Assert model_class.__name__ == 'GeneratedModel' in test so the fallback
  name cannot silently regress
Copy link
Copy Markdown
Contributor

@jkomyno jkomyno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @matingathani — really good catch on this one. I built and locally verified a refinement that I think is worth folding in before merge.

Empirical finding: pydantic.create_model crashes only on None, not on "". I confirmed this by running:

from pydantic import create_model
create_model(None, foo=(str, ...))   # TypeError (the bug)
create_model("",   foo=(str, ...))   # OK — produces a model named ''

So the original Copilot comment about preserving "" is technically right, but the current key-presence form leaves a real crash uncovered: {"title": None, ...} still hits TypeError: type.__new__() argument 1 must be str, not None. CrewAI / LangChain emit this shape in the wild when older Pydantic v1 paths serialize anonymous models with an explicit-None title.

Two suggestions below: one extends the prod fix to also cover None (preserving "" as Copilot requested); the other parametrizes the test to lock in both crash modes. Both verified locally against the full schema-parser suite (68 tests pass, ruff clean).

:return: Pydantic `BaseModel` type
"""
model_name = json_schema.get("title")
model_name = json_schema["title"] if "title" in json_schema else "GeneratedModel"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current form correctly handles a missing title key, but {"title": None, ...} still slips through and crashes create_model. Two-line fix that covers both shapes while preserving empty-string titles unchanged (matching Copilot's earlier feedback):

Suggested change
model_name = json_schema["title"] if "title" in json_schema else "GeneratedModel"
model_name = json_schema.get("title")
if model_name is None:
model_name = "GeneratedModel"

Comment on lines +306 to +327
def test_no_title_fallback(self):
"""Issue #2435: anonymous schemas (no 'title') must not crash with TypeError.

LangGraph/LangChain/CrewAI providers call json_schema_to_model for
array-item schemas that have no 'title' key. Without a fallback,
create_model(None, ...) raises TypeError: type.__new__() argument 1
must be str, not None.
"""
json_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"value": {"type": "integer"},
},
"required": ["name"],
}

model_class = json_schema_to_model(json_schema)
assert model_class.__name__ == "GeneratedModel"
instance = model_class(name="test", value=42)
assert instance.name == "test"
assert instance.value == 42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you take the shared.py suggestion above, this test can collapse into one parametrized case that pins both crash modes (missing key + explicit None). Sentinel ... distinguishes "key absent" from "key present with value None":

Suggested change
def test_no_title_fallback(self):
"""Issue #2435: anonymous schemas (no 'title') must not crash with TypeError.
LangGraph/LangChain/CrewAI providers call json_schema_to_model for
array-item schemas that have no 'title' key. Without a fallback,
create_model(None, ...) raises TypeError: type.__new__() argument 1
must be str, not None.
"""
json_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"value": {"type": "integer"},
},
"required": ["name"],
}
model_class = json_schema_to_model(json_schema)
assert model_class.__name__ == "GeneratedModel"
instance = model_class(name="test", value=42)
assert instance.name == "test"
assert instance.value == 42
@pytest.mark.parametrize(
"title_value",
[
pytest.param(..., id="missing"),
pytest.param(None, id="none"),
],
)
def test_none_title_falls_back_to_generated_model(self, title_value):
"""Issue #2435: schemas with missing or None title must not crash.
LangGraph/LangChain/CrewAI providers emit array-item schemas without a
usable titlethe key is absent, or present with an explicit None
value. Both forms used to crash create_model with
TypeError: type.__new__() argument 1 must be str, not None.
Empty-string titles are preserved (they don't crash create_model).
"""
json_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"value": {"type": "integer"},
},
"required": ["name"],
}
if title_value is not ...:
json_schema["title"] = title_value
model_class = json_schema_to_model(json_schema)
assert model_class.__name__ == "GeneratedModel"
instance = model_class(name="test", value=42)
assert instance.name == "test"
assert instance.value == 42

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: TypeError: type.__new__() argument 1 must be str, not None in json_schema_to_model (LangGraph Provider)

3 participants