Avoid top-level composition in tool schemas

This commit is contained in:
wuyang6
2026-05-11 11:51:11 +08:00
parent 0e2a00990f
commit 8cca3793e0
6 changed files with 53 additions and 46 deletions
@@ -1,8 +1,14 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"records": {"type": "array"}, "records": {
"records_path": {"type": "string"}, "type": "array",
"description": "Canonical records. Provide exactly one of records or records_path."
},
"records_path": {
"type": "string",
"description": "Path to records JSON/JSONL. Provide exactly one of records or records_path."
},
"output_path": {"type": "string"}, "output_path": {"type": "string"},
"output_dir": {"type": "string"}, "output_dir": {"type": "string"},
"output_format": { "output_format": {
@@ -13,9 +19,5 @@
"require_validation_ok": {"type": "boolean", "default": true}, "require_validation_ok": {"type": "boolean", "default": true},
"overwrite": {"type": "boolean", "default": true}, "overwrite": {"type": "boolean", "default": true},
"export_table": {"type": "boolean", "default": true} "export_table": {"type": "boolean", "default": true}
}, }
"anyOf": [
{"required": ["records"]},
{"required": ["records_path"]}
]
} }
@@ -1,15 +1,17 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"records": {"type": "array"}, "records": {
"records_path": {"type": "string"}, "type": "array",
"description": "Canonical records. Provide exactly one of records or records_path."
},
"records_path": {
"type": "string",
"description": "Path to records JSON/JSONL. Provide exactly one of records or records_path."
},
"output_path": {"type": "string"}, "output_path": {"type": "string"},
"output_dir": {"type": "string"}, "output_dir": {"type": "string"},
"require_validation_ok": {"type": "boolean", "default": true}, "require_validation_ok": {"type": "boolean", "default": true},
"overwrite": {"type": "boolean", "default": true} "overwrite": {"type": "boolean", "default": true}
}, }
"anyOf": [
{"required": ["records"]},
{"required": ["records_path"]}
]
} }
@@ -1,8 +1,14 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"draft_text": {"type": "string"}, "draft_text": {
"draft_path": {"type": "string"}, "type": "string",
"description": "Dataset draft text. Provide exactly one of draft_text or draft_path."
},
"draft_path": {
"type": "string",
"description": "Path to UTF-8 dataset draft text. Provide exactly one of draft_text or draft_path."
},
"batch_id": {"type": "string", "default": "aabbccdd"}, "batch_id": {"type": "string", "default": "aabbccdd"},
"source_type": { "source_type": {
"type": "string", "type": "string",
@@ -12,9 +18,5 @@
"base_timestamp": {"type": "integer"}, "base_timestamp": {"type": "integer"},
"timestamp_step_ms": {"type": "integer", "minimum": 1, "default": 60000}, "timestamp_step_ms": {"type": "integer", "minimum": 1, "default": 60000},
"default_request_id": {"type": "string", "default": "aabbccdd"} "default_request_id": {"type": "string", "default": "aabbccdd"}
}, }
"anyOf": [
{"required": ["draft_text"]},
{"required": ["draft_path"]}
]
} }
@@ -1,11 +1,13 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"records": {"type": "array"}, "records": {
"records_path": {"type": "string"} "type": "array",
"description": "Canonical records. Provide exactly one of records or records_path."
}, },
"anyOf": [ "records_path": {
{"required": ["records"]}, "type": "string",
{"required": ["records_path"]} "description": "Path to records JSON/JSONL. Provide exactly one of records or records_path."
] }
}
} }
+6 -18
View File
@@ -437,11 +437,11 @@ def build_data_agent_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentToo
'properties': { 'properties': {
'draft_text': { 'draft_text': {
'type': 'string', 'type': 'string',
'description': 'Dataset draft text in dataset draft text v1 format.', 'description': 'Dataset draft text in dataset draft text v1 format. Provide exactly one of draft_text or draft_path.',
}, },
'draft_path': { 'draft_path': {
'type': 'string', 'type': 'string',
'description': 'Path to a UTF-8 dataset draft text file. Prefer this for large drafts.', 'description': 'Path to a UTF-8 dataset draft text file. Prefer this for large drafts. Provide exactly one of draft_text or draft_path.',
}, },
'batch_id': { 'batch_id': {
'type': 'string', 'type': 'string',
@@ -469,10 +469,6 @@ def build_data_agent_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentToo
'description': 'Required for generated data; returned by data_agent_confirm_generation_plan.', 'description': 'Required for generated data; returned by data_agent_confirm_generation_plan.',
}, },
}, },
'anyOf': [
{'required': ['draft_text']},
{'required': ['draft_path']},
],
}, },
handler=resolve_handler(handlers, 'data_agent_normalize_dataset_draft', 'data-agent'), handler=resolve_handler(handlers, 'data_agent_normalize_dataset_draft', 'data-agent'),
), ),
@@ -490,17 +486,13 @@ def build_data_agent_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentToo
{'type': 'array'}, {'type': 'array'},
{'type': 'string'}, {'type': 'string'},
], ],
'description': 'Canonical records as an array, or a JSON string containing the array.', 'description': 'Canonical records as an array, or a JSON string containing the array. Provide exactly one of records or records_path.',
}, },
'records_path': { 'records_path': {
'type': 'string', 'type': 'string',
'description': 'Path to records JSON/JSONL. Prefer this when records are large.', 'description': 'Path to records JSON/JSONL. Prefer this when records are large. Provide exactly one of records or records_path.',
}, },
}, },
'anyOf': [
{'required': ['records']},
{'required': ['records_path']},
],
}, },
handler=resolve_handler(handlers, 'data_agent_validate_dataset_records', 'data-agent'), handler=resolve_handler(handlers, 'data_agent_validate_dataset_records', 'data-agent'),
), ),
@@ -519,11 +511,11 @@ def build_data_agent_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentToo
{'type': 'array'}, {'type': 'array'},
{'type': 'string'}, {'type': 'string'},
], ],
'description': 'Canonical records as an array, or a JSON string containing the array.', 'description': 'Canonical records as an array, or a JSON string containing the array. Provide exactly one of records or records_path.',
}, },
'records_path': { 'records_path': {
'type': 'string', 'type': 'string',
'description': 'Path to records JSON/JSONL. Prefer this when records are large.', 'description': 'Path to records JSON/JSONL. Prefer this when records are large. Provide exactly one of records or records_path.',
}, },
'output_path': { 'output_path': {
'type': 'string', 'type': 'string',
@@ -552,10 +544,6 @@ def build_data_agent_tools(handlers: Mapping[str, ToolHandler]) -> list[AgentToo
}, },
}, },
'required': ['output_path'], 'required': ['output_path'],
'anyOf': [
{'required': ['records']},
{'required': ['records_path']},
],
}, },
handler=resolve_handler(handlers, 'data_agent_export_dataset_records', 'data-agent'), handler=resolve_handler(handlers, 'data_agent_export_dataset_records', 'data-agent'),
), ),
+11
View File
@@ -647,6 +647,17 @@ target: Agent(tag='地图导航')
self.assertEqual(export_payload['record_count'], 1) self.assertEqual(export_payload['record_count'], 1)
self.assertTrue(table_exists) self.assertTrue(table_exists)
def test_tool_schemas_avoid_top_level_composition_keywords(self) -> None:
# 部分 Bedrock/Anthropic 兼容后端不接受顶层 anyOf/oneOf/allOf。
blocked_keywords = {'anyOf', 'oneOf', 'allOf'}
violations = [
name
for name, tool in default_tool_registry().items()
if blocked_keywords.intersection(tool.parameters)
]
self.assertEqual(violations, [])
def test_data_agent_tools_route_relative_outputs_to_session_output(self) -> None: def test_data_agent_tools_route_relative_outputs_to_session_output(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir) root = Path(tmp_dir)