Commit Graph

2 Commits

Author SHA1 Message Date
Gautam Sharma cb84856a16 fix(openai-agents): preserve array item schemas (#3819)
## Summary

Prevent the Python OpenAI Agents provider from rewriting valid JSON
Schema array item definitions.

- Stop adding `"type": "string"` when an `items` schema has no direct
`type`.
- Preserve item schemas using `anyOf`, `oneOf`, `$ref`, object
constraints, or `{}`.
  - Continue removing `examples`, `pattern`, and `default` as before.
  - Add regression coverage for each affected schema shape.

  ## Problem

The provider previously added `"type": "string"` to every array `items`
object without a direct
  type declaration.

  That changes the meaning of valid JSON Schemas. For example:

  ```json
  {
    "anyOf": [
      { "type": "object" },
      { "type": "null" }
    ]
  }

  became:

  {
    "type": "string",
    "anyOf": [
      { "type": "object" },
      { "type": "null" }
    ]
  }

Because both constraints apply, valid object and null values become
impossible. This could expose
misleading tool schemas to OpenAI Agents or cause correct tool arguments
to be rejected.

  ## Testing

  - OpenAI Agents provider tests — 6 passed
  - OpenAI Agents type-inference tests — 5 passed
  - Mypy passed
  - Ruff lint and formatting passed
  - Schema-preservation smoke test passed

Co-authored-by: GautamSharma99 <gautamsharme99067@gmail.com>
Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com>
2026-07-15 18:07:20 +04:00
ly-wang19 95b7b7f649 fix(openai_agents): deep-copy tool schema before stripping examples/default (#3632)
## Problem

`OpenAIAgentsProvider.wrap_tool` mutates the caller's
`Tool.input_parameters`. It shallow-copies the schema and then strips
keys from the nested property dicts **in place**:

```python
modified_schema = tool.input_parameters.copy()        # shallow copy
modified_schema["additionalProperties"] = False
_remove_examples_from_schema(modified_schema)          # del prop["examples"/"pattern"/"default"]
```

`_remove_examples_from_schema` recurses into `schema["properties"][*]`
and does `del prop_value["examples"]`, `del prop_value["pattern"]`, `del
prop_value["default"]`. Because `dict.copy()` is shallow,
`modified_schema["properties"]` (and every nested property dict) is the
**same object** as the original, so those deletions strip
`examples`/`pattern`/**`default`** out of the caller's
`Tool.input_parameters`.

Since essentially every Composio tool carries `default`/`examples` on
its properties, wrapping a tool silently corrupts its schema. Wrapping
the same `Tool` twice, reusing it with another provider, or inspecting
it after wrapping then sees the defaults gone.

This is the only wrap path using a shallow `.copy()` + nested mutation —
the rest of the codebase deep-copies before mutating schemas, e.g.
`composio/utils/shared.py` (`schema = copy.deepcopy(schema)`, with the
docstring *"The schema is deep-copied before any mutation so the
caller's original is never modified."*) and `Tools._get`
(`tool.model_copy(deep=True)`).

## Fix

Deep-copy the schema before mutating
(`copy.deepcopy(tool.input_parameters)`), so
`_remove_examples_from_schema` only edits the provider-local copy.
One-line change + `import copy`.

## Test

Added
`python/providers/openai_agents/tests/test_provider.py::test_wrap_tool_does_not_mutate_input_parameters`
— builds a tool whose properties carry `default`/`examples`/`pattern`,
snapshots `input_parameters`, calls `wrap_tool`, and asserts both sides
of the contract:

- the caller's original `Tool.input_parameters` is unchanged
- the returned `FunctionTool.params_json_schema` is still normalized for
OpenAI Agents (`additionalProperties: false`, with
`default`/`examples`/`pattern` stripped)

- **Before:** the assertion fails — `owner` loses `default`/`examples`,
`repo` loses `pattern`.
- **After:** `Tool.input_parameters` is untouched while the wrapped
schema remains sanitized.

Verification:

- `uv run pytest python/providers/openai_agents/tests/test_provider.py
-q` → **1 passed**
- `uv run ruff check
python/providers/openai_agents/composio_openai_agents/provider.py
python/providers/openai_agents/tests/test_provider.py` → clean
- `uv run ruff format --check
python/providers/openai_agents/composio_openai_agents/provider.py
python/providers/openai_agents/tests/test_provider.py` → clean

Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
2026-06-25 01:59:11 +04:00