diff --git a/tests/test_gateway.py b/tests/test_gateway.py index 15c7d77..4536869 100644 --- a/tests/test_gateway.py +++ b/tests/test_gateway.py @@ -1025,6 +1025,84 @@ def test_chat_extra_routes_gateway_field_to_extra_body( assert call["extra_body"] == {"document_dpi": 200} assert call["temperature"] == 0 + def test_chat_document_dpi_rides_in_extra_body(self, runner, patched_cli, tmp_path): + pdf = tmp_path / "doc.pdf" + pdf.write_bytes(b"%PDF fake") + result = runner.invoke( + app, + ["gw", "chat", str(pdf), "-m", "pp-ocrv6", "--document-dpi", "150", "-ns"], + ) + assert result.exit_code == 0, result.stdout + call = patched_cli["client"].gateway.completions.calls[-1] + assert call["extra_body"] == {"document_dpi": 150} + # A gateway field must not leak into create()'s own kwargs. + assert "document_dpi" not in call + + def test_chat_document_dpi_short_flag(self, runner, patched_cli, tmp_path): + pdf = tmp_path / "doc.pdf" + pdf.write_bytes(b"%PDF fake") + result = runner.invoke( + app, ["gw", "chat", str(pdf), "-m", "pp-ocrv6", "-d", "300", "-ns"] + ) + assert result.exit_code == 0, result.stdout + assert patched_cli["client"].gateway.completions.calls[-1]["extra_body"] == { + "document_dpi": 300 + } + + def test_chat_document_max_pages_rides_in_extra_body( + self, runner, patched_cli, tmp_path + ): + pdf = tmp_path / "doc.pdf" + pdf.write_bytes(b"%PDF fake") + result = runner.invoke( + app, + [ + "gw", + "chat", + str(pdf), + "-m", + "pp-ocrv6", + "--document-max-pages", + "10", + "-ns", + ], + ) + assert result.exit_code == 0, result.stdout + assert patched_cli["client"].gateway.completions.calls[-1]["extra_body"] == { + "document_max_pages": 10 + } + + def test_chat_dpi_merges_with_method_and_extra(self, runner, patched_cli, tmp_path): + pdf = tmp_path / "doc.pdf" + pdf.write_bytes(b"%PDF fake") + result = runner.invoke( + app, + [ + "gw", + "chat", + str(pdf), + "-m", + "pp-ocrv6", + "--method", + "ocr", + "--document-dpi", + "150", + "--document-max-pages", + "50", + "-e", + "temperature=0", + "-ns", + ], + ) + assert result.exit_code == 0, result.stdout + call = patched_cli["client"].gateway.completions.calls[-1] + assert call["extra_body"] == { + "method": "ocr", + "document_dpi": 150, + "document_max_pages": 50, + } + assert call["temperature"] == 0 + def test_chat_no_extra_body_when_unused(self, runner, patched_cli, tmp_path): f = tmp_path / "img.png" f.write_bytes(b"fakepng") diff --git a/vlmrun/cli/_cli/gateway.py b/vlmrun/cli/_cli/gateway.py index 4c6fb3c..3d4bfec 100644 --- a/vlmrun/cli/_cli/gateway.py +++ b/vlmrun/cli/_cli/gateway.py @@ -12,6 +12,11 @@ such as ``zai-org/glm-ocr`` and ``paddleocr/pp-ocrv6``) do not accept text-only input. +Multi-page PDFs are sent whole; the gateway rasterizes and fans them out per +page. ``--document-dpi`` tunes that rasterization (default 72; raise to +150-300 for dense or high-resolution pages) and ``--document-max-pages`` caps +how many pages are processed. + Commands: ``health``, ``models`` (list or detail one model), ``chat``, ``embed`` (embeddings) and ``transcribe`` (audio transcriptions). """ @@ -55,6 +60,7 @@ vlmrun gw chat a.pdf b.pdf -m paddleocr/pp-ocrv6 vlmrun gw chat img.jpg -m paddleocr/pp-ocrv6 vlmrun gw chat img.jpg -p "describe this image" -m qwen/qwen3.5-0.8b + vlmrun gw chat doc.pdf -m paddleocr/pp-ocrv6 --document-dpi 150 vlmrun gw chat doc.pdf -m zai-org/glm-ocr -e temperature=0 -e max_tokens=4096 \b @@ -71,6 +77,8 @@ aliases (e.g. `glm-ocr`) also work. Most gateway models (e.g. OCR models) require at least one input file and do not accept text-only prompts. Use -p only for models that support it. + PDFs are sent whole; the gateway rasterizes and fans them out per page. Raise + `--document-dpi` (default 72) to 150-300 if small or dense text is missed. """ GATEWAY_HELP = """OCR, VLM, embedding and transcription models on the VLM Run gateway. @@ -562,6 +570,21 @@ def chat( "--method-params", help='JSON object of method arguments, e.g. \'{"lang": "en"}\'.', ), + document_dpi: Optional[int] = typer.Option( + None, + "--document-dpi", + "-d", + help=( + "Rasterization DPI per PDF page (gateway default 72; 150 is a good " + "balance; 300+ preserves fine print on dense/high-res pages at " + "higher cost). PDF input only." + ), + ), + document_max_pages: Optional[int] = typer.Option( + None, + "--document-max-pages", + help="Cap the number of PDF pages processed (gateway default 500).", + ), response_format: Optional[str] = typer.Option( None, "--response-format", @@ -614,6 +637,13 @@ def chat( raise typer.Exit(1) extra_body["method_params"] = parsed_params + # Document rasterization controls are gateway-specific, so they ride in + # extra_body as top-level request-body fields (like `method`). + if document_dpi is not None: + extra_body["document_dpi"] = document_dpi + if document_max_pages is not None: + extra_body["document_max_pages"] = document_max_pages + if response_format: # A standard OpenAI create() field, so it rides as a top-level kwarg. create_kwargs["response_format"] = _parse_response_format(response_format)