From 53773cd5fc25e539a8d136276449a44338bd7aec Mon Sep 17 00:00:00 2001 From: Yiyabo Date: Sun, 19 Jul 2026 21:47:06 +0800 Subject: [PATCH] fix: tighten input_schema per Volcengine Jimeng 3.0 Pro contract - frames: enum [121, 241] (was minimum 1) - prompt: maxLength 800 (was 2000) - seed: minimum -1 (was unbounded) - Add 9 schema validation rejection tests - Add authoritative API reference link to PROVIDERS.md - Document CVSync2Async* route choice and schema constraints Fixes calesthio's second review feedback on PR #341. --- docs/PROVIDERS.md | 9 +++++ tests/contracts/test_jimeng_video.py | 60 ++++++++++++++++++++++++++++ tools/video/jimeng_video.py | 6 ++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 0dc38b18..02f3f7e3 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -123,8 +123,17 @@ Authentication uses Volcengine IAM V4 signing (HMAC-SHA256), not a Bearer token. API flow: `POST ?Action=CVSync2AsyncSubmitTask` → poll `POST ?Action=CVSync2AsyncGetResult` → download `video_url`. +The implementation uses the compatible generic `CVSync2Async*` route (API version `2022-08-31`) rather than the model-specific `2024-06-06` actions presented in the public API explorer. This is intentional — the generic route supports the same Jimeng 3.0 Pro model via `req_key` while remaining stable across model updates. + The `req_key` for video is `jimeng_ti2v_v30_pro`. Success code is `10000`. Task statuses: `in_queue`, `generating`, `done`, `not_found`, `expired`. +**Authoritative API reference:** [Jimeng TI2V V30 Pro SubmitTask](https://api.volcengine.com/api-docs/view?action=JimengTI2VV30PROSubmitTask&serviceCode=cv&version=2024-06-06) + +**Schema constraints** (enforced by `input_schema` to prevent paid-call failures): +- `prompt`: max 800 characters +- `frames`: must be exactly `121` (5s) or `241` (10s) at 24fps +- `seed`: `-1` for random, or any non-negative integer + #### Pricing | Model | Price | diff --git a/tests/contracts/test_jimeng_video.py b/tests/contracts/test_jimeng_video.py index 5cbf5dde..5d060dba 100644 --- a/tests/contracts/test_jimeng_video.py +++ b/tests/contracts/test_jimeng_video.py @@ -318,3 +318,63 @@ class TestRegistryDiscovery: jimeng = [t for t in registry._tools.values() if t.name == "jimeng_video"] assert len(jimeng) == 1 assert jimeng[0].provider == "volcengine" + + +# ------------------------------------------------------------------ +# Schema validation — reject invalid inputs before paid API call +# ------------------------------------------------------------------ + +class TestSchemaValidation: + + def test_frames_accepts_121(self): + schema = JimengVideo().input_schema + valid = schema["properties"]["frames"] + assert valid["enum"] == [121, 241] + + def test_frames_rejects_non_enum(self): + import jsonschema + schema = JimengVideo().input_schema + for invalid in [1, 100, 200, 500, 0, -1]: + instance = {"prompt": "test", "frames": invalid} + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate(instance, schema) + + def test_prompt_max_length_800(self): + schema = JimengVideo().input_schema + assert schema["properties"]["prompt"]["maxLength"] == 800 + + def test_prompt_rejects_over_800_chars(self): + import jsonschema + schema = JimengVideo().input_schema + instance = {"prompt": "x" * 801} + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate(instance, schema) + + def test_prompt_accepts_800_chars(self): + import jsonschema + schema = JimengVideo().input_schema + instance = {"prompt": "x" * 800} + jsonschema.validate(instance, schema) + + def test_seed_minimum_is_negative_one(self): + schema = JimengVideo().input_schema + assert schema["properties"]["seed"]["minimum"] == -1 + + def test_seed_rejects_below_negative_one(self): + import jsonschema + schema = JimengVideo().input_schema + for invalid in [-2, -10, -100]: + instance = {"prompt": "test", "seed": invalid} + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate(instance, schema) + + def test_seed_accepts_negative_one(self): + import jsonschema + schema = JimengVideo().input_schema + jsonschema.validate({"prompt": "test", "seed": -1}, schema) + + def test_seed_accepts_zero_and_positive(self): + import jsonschema + schema = JimengVideo().input_schema + for valid in [0, 1, 42, 999999]: + jsonschema.validate({"prompt": "test", "seed": valid}, schema) diff --git a/tools/video/jimeng_video.py b/tools/video/jimeng_video.py index 3208a05f..9410b864 100644 --- a/tools/video/jimeng_video.py +++ b/tools/video/jimeng_video.py @@ -86,7 +86,8 @@ class JimengVideo(BaseTool): "properties": { "prompt": { "type": "string", - "description": "Video description. Max 2000 chars. Supports Chinese.", + "maxLength": 800, + "description": "Video description. Max 800 chars. Supports Chinese.", }, "operation": { "type": "string", @@ -102,7 +103,7 @@ class JimengVideo(BaseTool): }, "frames": { "type": "integer", - "minimum": 1, + "enum": [121, 241], "default": 121, "description": "Total frames. 121=5s, 241=10s at 24fps.", }, @@ -113,6 +114,7 @@ class JimengVideo(BaseTool): }, "seed": { "type": "integer", + "minimum": -1, "default": -1, "description": "Random seed. -1 for random.", },