Browse Source

Enhance FastAPI apps (#2533)

* Bump bce-python-sdk version

* Fix type hints

* Fix type hint

* Fix

* Fix

* Polish error messages

* Remove unused variables

* Fix typing

* Polish typing

* Support setting use_hpip in config file

* Fix import

* Update docs:

* Fix typos

* Fix and add _build_models

* Fix bug

* formula recognition and seal recognition FastAPI apps support pdf input

* Rename

* Refactor OCR pipeline apps and add threshold for multi_label_image_classification

* Support data_info and update OCR pipelines

* Update docs

* Fix ppchatocrv3 typing

* Fix bug
Lin Manhui 11 tháng trước cách đây
mục cha
commit
33ab80c75b
70 tập tin đã thay đổi với 1613 bổ sung2105 xóa
  1. 66 0
      docs/pipeline_usage/tutorials/cv_pipelines/face_recognition.en.md
  2. 66 0
      docs/pipeline_usage/tutorials/cv_pipelines/face_recognition.md
  3. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/general_image_recognition.en.md
  4. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/general_image_recognition.md
  5. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/image_anomaly_detection.en.md
  6. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/image_anomaly_detection.md
  7. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/image_classification.en.md
  8. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/image_classification.md
  9. 18 4
      docs/pipeline_usage/tutorials/cv_pipelines/image_multi_label_classification.en.md
  10. 18 4
      docs/pipeline_usage/tutorials/cv_pipelines/image_multi_label_classification.md
  11. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/instance_segmentation.en.md
  12. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/instance_segmentation.md
  13. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/object_detection.en.md
  14. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/object_detection.md
  15. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/pedestrian_attribute_recognition.en.md
  16. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/pedestrian_attribute_recognition.md
  17. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/semantic_segmentation.en.md
  18. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/semantic_segmentation.md
  19. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/small_object_detection.en.md
  20. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/small_object_detection.md
  21. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/vehicle_attribute_recognition.en.md
  22. 15 1
      docs/pipeline_usage/tutorials/cv_pipelines/vehicle_attribute_recognition.md
  23. 28 9
      docs/pipeline_usage/tutorials/information_extraction_pipelines/document_scene_information_extraction.en.md
  24. 25 6
      docs/pipeline_usage/tutorials/information_extraction_pipelines/document_scene_information_extraction.md
  25. 15 1
      docs/pipeline_usage/tutorials/ocr_pipelines/OCR.en.md
  26. 15 1
      docs/pipeline_usage/tutorials/ocr_pipelines/OCR.md
  27. 63 360
      docs/pipeline_usage/tutorials/ocr_pipelines/formula_recognition.en.md
  28. 64 382
      docs/pipeline_usage/tutorials/ocr_pipelines/formula_recognition.md
  29. 32 16
      docs/pipeline_usage/tutorials/ocr_pipelines/layout_parsing.en.md
  30. 22 3
      docs/pipeline_usage/tutorials/ocr_pipelines/layout_parsing.md
  31. 67 388
      docs/pipeline_usage/tutorials/ocr_pipelines/seal_recognition.en.md
  32. 66 408
      docs/pipeline_usage/tutorials/ocr_pipelines/seal_recognition.md
  33. 15 1
      docs/pipeline_usage/tutorials/ocr_pipelines/table_recognition.en.md
  34. 15 1
      docs/pipeline_usage/tutorials/ocr_pipelines/table_recognition.md
  35. 15 1
      docs/pipeline_usage/tutorials/time_series_pipelines/time_series_anomaly_detection.en.md
  36. 15 1
      docs/pipeline_usage/tutorials/time_series_pipelines/time_series_anomaly_detection.md
  37. 15 1
      docs/pipeline_usage/tutorials/time_series_pipelines/time_series_classification.en.md
  38. 15 1
      docs/pipeline_usage/tutorials/time_series_pipelines/time_series_classification.md
  39. 15 1
      docs/pipeline_usage/tutorials/time_series_pipelines/time_series_forecasting.en.md
  40. 15 1
      docs/pipeline_usage/tutorials/time_series_pipelines/time_series_forecasting.md
  41. 3 2
      paddlex/inference/pipelines/__init__.py
  42. 4 2
      paddlex/inference/pipelines/pp_shitu_v2.py
  43. 5 0
      paddlex/inference/pipelines/ppchatocrv3/ppchatocrv3.py
  44. 13 0
      paddlex/inference/pipelines/serving/_pipeline_apps/_common/__init__.py
  45. 49 0
      paddlex/inference/pipelines/serving/_pipeline_apps/_common/cv.py
  46. 152 0
      paddlex/inference/pipelines/serving/_pipeline_apps/_common/ocr.py
  47. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/anomaly_detection.py
  48. 23 28
      paddlex/inference/pipelines/serving/_pipeline_apps/face_recognition.py
  49. 64 44
      paddlex/inference/pipelines/serving/_pipeline_apps/formula_recognition.py
  50. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/image_classification.py
  51. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/instance_segmentation.py
  52. 21 83
      paddlex/inference/pipelines/serving/_pipeline_apps/layout_parsing.py
  53. 20 8
      paddlex/inference/pipelines/serving/_pipeline_apps/multi_label_image_classification.py
  54. 8 7
      paddlex/inference/pipelines/serving/_pipeline_apps/object_detection.py
  55. 10 7
      paddlex/inference/pipelines/serving/_pipeline_apps/ocr.py
  56. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/pedestrian_attribute_recognition.py
  57. 24 29
      paddlex/inference/pipelines/serving/_pipeline_apps/pp_shitu_v2.py
  58. 44 129
      paddlex/inference/pipelines/serving/_pipeline_apps/ppchatocrv3.py
  59. 54 43
      paddlex/inference/pipelines/serving/_pipeline_apps/seal_recognition.py
  60. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/semantic_segmentation.py
  61. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/small_object_detection.py
  62. 8 7
      paddlex/inference/pipelines/serving/_pipeline_apps/table_recognition.py
  63. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/ts_ad.py
  64. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/ts_cls.py
  65. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/ts_fc.py
  66. 6 7
      paddlex/inference/pipelines/serving/_pipeline_apps/vehicle_attribute_recognition.py
  67. 9 6
      paddlex/inference/pipelines/serving/app.py
  68. 31 3
      paddlex/inference/pipelines/serving/models.py
  69. 55 28
      paddlex/inference/pipelines/serving/utils.py
  70. 1 1
      paddlex/serving_requirements.txt

+ 66 - 0
docs/pipeline_usage/tutorials/cv_pipelines/face_recognition.en.md

@@ -399,6 +399,72 @@ Below are the API reference and multi-language service invocation examples:
 
 <details><summary>API Reference</summary>
 
+<p>For main operations provided by the service:</p>
+<ul>
+<li>The HTTP request method is POST.</li>
+<li>The request body and the response body are both JSON data (JSON objects).</li>
+<li>When the request is successfully processed, the response status code is <code>200</code>, and the attributes of the response body are as follows:</li>
+</ul>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Type</th>
+<th>Meaning</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
+<td><code>errorCode</code></td>
+<td><code>integer</code></td>
+<td>Error code. Fixed to <code>0</code>.</td>
+</tr>
+<tr>
+<td><code>errorMsg</code></td>
+<td><code>string</code></td>
+<td>Error description. Fixed to <code>"Success"</code>.</td>
+</tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
+</tbody>
+</table>
+<ul>
+<li>When the request is not successfully processed, the attributes of the response body are as follows:</li>
+</ul>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Type</th>
+<th>Meaning</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
+<td><code>errorCode</code></td>
+<td><code>integer</code></td>
+<td>Error code. Same as the response status code.</td>
+</tr>
+<tr>
+<td><code>errorMsg</code></td>
+<td><code>string</code></td>
+<td>Error description.</td>
+</tr>
+</tbody>
+</table>
 <p>The main operations provided by the service are as follows:</p>
 <ul>
 <li><b><code>buildIndex</code></b></li>

+ 66 - 0
docs/pipeline_usage/tutorials/cv_pipelines/face_recognition.md

@@ -429,6 +429,72 @@ data_root             # 数据集根目录,目录名称可以改变
 
 <details><summary>API参考</summary>
 
+<p>对于服务提供的主要操作:</p>
+<ul>
+<li>HTTP请求方法为POST。</li>
+<li>请求体和响应体均为JSON数据(JSON对象)。</li>
+<li>当请求处理成功时,响应状态码为<code>200</code>,响应体的属性如下:</li>
+</ul>
+<table>
+<thead>
+<tr>
+<th>名称</th>
+<th>类型</th>
+<th>含义</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
+<td><code>errorCode</code></td>
+<td><code>integer</code></td>
+<td>错误码。固定为<code>0</code>。</td>
+</tr>
+<tr>
+<td><code>errorMsg</code></td>
+<td><code>string</code></td>
+<td>错误说明。固定为<code>"Success"</code>。</td>
+</tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
+</tbody>
+</table>
+<ul>
+<li>当请求处理未成功时,响应体的属性如下:</li>
+</ul>
+<table>
+<thead>
+<tr>
+<th>名称</th>
+<th>类型</th>
+<th>含义</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
+<td><code>errorCode</code></td>
+<td><code>integer</code></td>
+<td>错误码。与响应状态码相同。</td>
+</tr>
+<tr>
+<td><code>errorMsg</code></td>
+<td><code>string</code></td>
+<td>错误说明。</td>
+</tr>
+</tbody>
+</table>
 <p>服务提供的主要操作如下:</p>
 <ul>
 <li><b><code>buildIndex</code></b></li>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/general_image_recognition.en.md

@@ -384,6 +384,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed to <code>0</code>.</td>
@@ -393,9 +398,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed to <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property, which is an <code>object</code> type that stores operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the properties of the response body are as follows:</li>
 </ul>
@@ -409,6 +418,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/general_image_recognition.md

@@ -415,6 +415,11 @@ data_root             # 数据集根目录,目录名称可以改变
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -424,9 +429,13 @@ data_root             # 数据集根目录,目录名称可以改变
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -440,6 +449,11 @@ data_root             # 数据集根目录,目录名称可以改变
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/image_anomaly_detection.en.md

@@ -233,6 +233,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -242,9 +247,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -258,6 +267,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/image_anomaly_detection.md

@@ -232,6 +232,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -241,9 +246,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -257,6 +266,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/image_classification.en.md

@@ -894,6 +894,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -903,9 +908,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -919,6 +928,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/image_classification.md

@@ -886,6 +886,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -895,9 +900,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -911,6 +920,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 18 - 4
docs/pipeline_usage/tutorials/cv_pipelines/image_multi_label_classification.en.md

@@ -257,6 +257,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed to <code>0</code>.</td>
@@ -266,9 +271,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed to <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -282,6 +291,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>
@@ -338,9 +352,9 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
-<td><code>topK</code></td>
-<td><code>integer</code></td>
-<td>Only the top <code>topK</code> categories with the highest scores will be retained in the result.</td>
+<td><code>threshold</code></td>
+<td><code>number</code></td>
+<td>Category score threshold.</td>
 <td>No</td>
 </tr>
 </tbody>

+ 18 - 4
docs/pipeline_usage/tutorials/cv_pipelines/image_multi_label_classification.md

@@ -258,6 +258,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -267,9 +272,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -283,6 +292,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>
@@ -339,9 +353,9 @@ for res in output:
 </thead>
 <tbody>
 <tr>
-<td><code>topK</code></td>
-<td><code>integer</code></td>
-<td>结果中将只保留得分最高的<code>topK</code>个类别。</td>
+<td><code>threshold</code></td>
+<td><code>number</code></td>
+<td>类别得分阈值。</td>
 <td>否</td>
 </tr>
 </tbody>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/instance_segmentation.en.md

@@ -380,6 +380,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -389,9 +394,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -405,6 +414,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/instance_segmentation.md

@@ -375,6 +375,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -384,9 +389,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -400,6 +409,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/object_detection.en.md

@@ -566,6 +566,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -575,9 +580,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -591,6 +600,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/object_detection.md

@@ -565,6 +565,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -574,9 +579,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -590,6 +599,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/pedestrian_attribute_recognition.en.md

@@ -265,6 +265,11 @@ Below are the API reference and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -274,9 +279,13 @@ Below are the API reference and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -290,6 +299,11 @@ Below are the API reference and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/pedestrian_attribute_recognition.md

@@ -265,6 +265,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -274,9 +279,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -290,6 +299,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/semantic_segmentation.en.md

@@ -406,6 +406,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -415,9 +420,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -431,6 +440,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/semantic_segmentation.md

@@ -405,6 +405,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -414,9 +419,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -430,6 +439,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/small_object_detection.en.md

@@ -254,6 +254,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -263,9 +268,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -279,6 +288,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/small_object_detection.md

@@ -252,6 +252,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -261,9 +266,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -277,6 +286,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/vehicle_attribute_recognition.en.md

@@ -262,6 +262,11 @@ Below are the API reference and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -271,9 +276,13 @@ Below are the API reference and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -287,6 +296,11 @@ Below are the API reference and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/cv_pipelines/vehicle_attribute_recognition.md

@@ -262,6 +262,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -271,9 +276,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -287,6 +296,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 28 - 9
docs/pipeline_usage/tutorials/information_extraction_pipelines/document_scene_information_extraction.en.md

@@ -641,6 +641,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -650,9 +655,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -666,6 +675,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>
@@ -715,7 +729,7 @@ Below are the API references and multi-language service invocation examples:
 <td>No</td>
 </tr>
 <tr>
-<td><code>useImgUnwrapping</code></td>
+<td><code>useImgUnwarping</code></td>
 <td><code>boolean</code></td>
 <td>Whether to enable text image correction. This feature is enabled by default.</td>
 <td>No</td>
@@ -775,6 +789,11 @@ Below are the API references and multi-language service invocation examples:
 <td><code>object</code></td>
 <td>Key information in the image, which can be used as input for other operations.</td>
 </tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>Information about the input data.</td>
+</tr>
 </tbody>
 </table>
 <p>Each element in <code>visionResults</code> is an <code>object</code> with the following properties:</p>
@@ -1147,18 +1166,18 @@ Below are the API references and multi-language service invocation examples:
 <tbody>
 <tr>
 <td><code>ocr</code></td>
-<td><code>string</code></td>
-<td>OCR prompt.</td>
+<td><code>array</code></td>
+<td>OCR prompts.</td>
 </tr>
 <tr>
 <td><code>table</code></td>
-<td><code>string</code></td>
-<td>Table prompt.</td>
+<td><code>array</code></td>
+<td>Table prompts.</td>
 </tr>
 <tr>
 <td><code>html</code></td>
-<td><code>string</code></td>
-<td>HTML prompt.</td>
+<td><code>array</code></td>
+<td>HTML prompts.</td>
 </tr>
 </tbody>
 </table></details>
@@ -1196,7 +1215,7 @@ payload = {
     &quot;file&quot;: file_data,
     &quot;fileType&quot;: 1,
     &quot;useImgOrientationCls&quot;: True,
-    &quot;useImgUnwrapping&quot;: True,
+    &quot;useImgUnwarping&quot;: True,
     &quot;useSealTextDet&quot;: True,
 }
 resp_vision = requests.post(url=f&quot;{API_BASE_URL}/chatocr-vision&quot;, json=payload)

+ 25 - 6
docs/pipeline_usage/tutorials/information_extraction_pipelines/document_scene_information_extraction.md

@@ -610,6 +610,11 @@ chat_result.print()
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -619,9 +624,13 @@ chat_result.print()
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -635,6 +644,11 @@ chat_result.print()
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>
@@ -684,7 +698,7 @@ chat_result.print()
 <td>否</td>
 </tr>
 <tr>
-<td><code>useImgUnwrapping</code></td>
+<td><code>useImgUnwarping</code></td>
 <td><code>boolean</code></td>
 <td>是否启用文本图像矫正功能。默认启用该功能。</td>
 <td>否</td>
@@ -744,6 +758,11 @@ chat_result.print()
 <td><code>object</code></td>
 <td>图像中的关键信息,可用作其他操作的输入。</td>
 </tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>输入数据信息。</td>
+</tr>
 </tbody>
 </table>
 <p><code>visionResults</code>中的每个元素为一个<code>object</code>,具有如下属性:</p>
@@ -1116,17 +1135,17 @@ chat_result.print()
 <tbody>
 <tr>
 <td><code>ocr</code></td>
-<td><code>string</code></td>
+<td><code>array</code></td>
 <td>OCR提示词。</td>
 </tr>
 <tr>
 <td><code>table</code></td>
-<td><code>string</code></td>
+<td><code>array</code></td>
 <td>表格提示词。</td>
 </tr>
 <tr>
 <td><code>html</code></td>
-<td><code>string</code></td>
+<td><code>array</code></td>
 <td>HTML提示词。</td>
 </tr>
 </tbody>
@@ -1166,7 +1185,7 @@ payload = {
     &quot;file&quot;: file_data,
     &quot;fileType&quot;: 1,
     &quot;useImgOrientationCls&quot;: True,
-    &quot;useImgUnwrapping&quot;: True,
+    &quot;useImgUnwarping&quot;: True,
     &quot;useSealTextDet&quot;: True,
 }
 resp_vision = requests.post(url=f&quot;{API_BASE_URL}/chatocr-vision&quot;, json=payload)

+ 15 - 1
docs/pipeline_usage/tutorials/ocr_pipelines/OCR.en.md

@@ -323,6 +323,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -332,9 +337,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -348,6 +357,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/ocr_pipelines/OCR.md

@@ -325,6 +325,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -334,9 +339,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -350,6 +359,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 63 - 360
docs/pipeline_usage/tutorials/ocr_pipelines/formula_recognition.en.md

@@ -305,6 +305,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -314,9 +319,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -330,6 +339,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>
@@ -361,12 +375,18 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
-<td><code>image</code></td>
+<td><code>file</code></td>
 <td><code>string</code></td>
-<td>The URL of an image file accessible by the service or the Base64 encoded result of the image file content.</td>
+<td>The URL of an image file or PDF file accessible by the service, or the Base64 encoded result of the content of the above-mentioned file types. For PDF files with more than 10 pages, only the content of the first 10 pages will be used.</td>
 <td>Yes</td>
 </tr>
 <tr>
+<td><code>fileType</code></td>
+<td><code>integer</code></td>
+<td>File type. <code>0</code> indicates a PDF file, and <code>1</code> indicates an image file. If this property is not present in the request body, the service will attempt to infer the file type automatically based on the URL.</td>
+<td>No</td>
+</tr>
+<tr>
 <td><code>inferenceParams</code></td>
 <td><code>object</code></td>
 <td>Inference parameters.</td>
@@ -406,11 +426,38 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>formulaRecResults</code></td>
+<td><code>array</code></td>
+<td>Formula recognition results. The array length is 1 (for image input) or the smaller of the number of document pages and 10 (for PDF input). For PDF input, each element in the array represents the processing result of each page in the PDF file.</td>
+</tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>Information about the input data.</td>
+</tr>
+</tbody>
+</table>
+<p>Each element in <code>formulaRecResults</code> is an <code>object</code> with the following properties:</p>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Type</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
 <td><code>formulas</code></td>
 <td><code>array</code></td>
 <td>Positions and contents of formulas.</td>
 </tr>
 <tr>
+<td><code>inputImage</code></td>
+<td><code>string</code></td>
+<td>Input image. The image is in JPEG format and encoded using Base64.</td>
+</tr>
+<tr>
 <td><code>layoutImage</code></td>
 <td><code>string</code></td>
 <td>Layout area detection result image. The image is in JPEG format and encoded using Base64.</td>
@@ -444,35 +491,7 @@ Below are the API references and multi-language service invocation examples:
 </tr>
 </tbody>
 </table>
-<p>Example of <code>result</code>:</p>
-<pre><code class="language-json">{
-&quot;formulas&quot;: [
-{
-&quot;poly&quot;: [
-[
-444.0,
-244.0
-],
-[
-705.4,
-244.5
-],
-[
-705.8,
-311.3
-],
-[
-444.1,
-311.0
-]
-],
-&quot;latex&quot;: &quot;F({\bf x})=C(F_{1}(x_{1}),\cdot\cdot\cdot,F_{N}(x_{N})).\qquad\qquad\qquad(1)&quot;
-}
-],
-&quot;layoutImage&quot;: &quot;xxxxxx&quot;,
-&quot;ocrImage&quot;: &quot;xxxxxx&quot;
-}
-</code></pre></details>
+</details>
 
 <details><summary>Multi-Language Service Invocation Examples</summary>
 
@@ -484,341 +503,25 @@ Below are the API references and multi-language service invocation examples:
 import requests
 
 API_URL = &quot;http://localhost:8080/formula-recognition&quot;
-image_path = &quot;./demo.jpg&quot;
-layout_image_path = &quot;./layout.jpg&quot;
+file_path = &quot;./demo.jpg&quot;
 
-with open(image_path, &quot;rb&quot;) as file:
-    image_bytes = file.read()
-    image_data = base64.b64encode(image_bytes).decode(&quot;ascii&quot;)
+with open(file_path, &quot;rb&quot;) as file:
+    file_bytes = file.read()
+    file_data = base64.b64encode(file_bytes).decode(&quot;ascii&quot;)
 
-payload = {&quot;image&quot;: image_data}
+payload = {&quot;file&quot;: file_data, &quot;fileType&quot;: 1}
 
 response = requests.post(API_URL, json=payload)
 
 assert response.status_code == 200
 result = response.json()[&quot;result&quot;]
-with open(layout_image_path, &quot;wb&quot;) as file:
-    file.write(base64.b64decode(result[&quot;layoutImage&quot;]))
-print(f&quot;Output image saved at {layout_image_path}&quot;)
-print(&quot;\nDetected formulas:&quot;)
-print(result[&quot;formulas&quot;])
-</code></pre></details>
-
-<details><summary>C++</summary>
-
-<pre><code class="language-cpp">#include &lt;iostream&gt;
-#include &quot;cpp-httplib/httplib.h&quot; // https://github.com/Huiyicc/cpp-httplib
-#include &quot;nlohmann/json.hpp&quot; // https://github.com/nlohmann/json
-#include &quot;base64.hpp&quot; // https://github.com/tobiaslocker/base64
-
-int main() {
-    httplib::Client client(&quot;localhost:8080&quot;);
-    const std::string imagePath = &quot;./demo.jpg&quot;;
-    const std::string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    httplib::Headers headers = {
-        {&quot;Content-Type&quot;, &quot;application/json&quot;}
-    };
-
-    std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
-    std::streamsize size = file.tellg();
-    file.seekg(0, std::ios::beg);
-
-    std::vector&lt;char&gt; buffer(size);
-    if (!file.read(buffer.data(), size)) {
-        std::cerr &lt;&lt; &quot;Error reading file.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-    std::string bufferStr(reinterpret_cast&lt;const char*&gt;(buffer.data()), buffer.size());
-    std::string encodedImage = base64::to_base64(bufferStr);
-
-    nlohmann::json jsonObj;
-    jsonObj[&quot;image&quot;] = encodedImage;
-    std::string body = jsonObj.dump();
-
-    auto response = client.Post(&quot;/formula-recognition&quot;, headers, body, &quot;application/json&quot;);
-    if (response &amp;&amp; response-&gt;status == 200) {
-        nlohmann::json jsonResponse = nlohmann::json::parse(response-&gt;body);
-        auto result = jsonResponse[&quot;result&quot;];
-
-        encodedImage = result[&quot;layoutImage&quot;];
-        decodedString = base64::from_base64(encodedImage);
-        std::vector&lt;unsigned char&gt; decodedLayoutImage(decodedString.begin(), decodedString.end());
-        std::ofstream outputLayoutFile(layoutImagePath, std::ios::binary | std::ios::out);
-        if (outputLayoutFile.is_open()) {
-            outputLayoutFile.write(reinterpret_cast&lt;char*&gt;(decodedLayoutImage.data()), decodedLayoutImage.size());
-            outputLayoutFile.close();
-            std::cout &lt;&lt; &quot;Output image saved at &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        } else {
-            std::cerr &lt;&lt; &quot;Unable to open file for writing: &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        }
-
-        auto formulas = result[&quot;formulas&quot;];
-        std::cout &lt;&lt; &quot;\nDetected formulas:&quot; &lt;&lt; std::endl;
-        for (const auto&amp; formula : formulas) {
-            std::cout &lt;&lt; formula &lt;&lt; std::endl;
-        }
-    } else {
-        std::cout &lt;&lt; &quot;Failed to send HTTP request.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-
-    return 0;
-}
-</code></pre></details>
-
-<details><summary>Java</summary>
-
-<pre><code class="language-java">import okhttp3.*;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Base64;
-
-public class Main {
-    public static void main(String[] args) throws IOException {
-        String API_URL = &quot;http://localhost:8080/formula-recognition&quot;;
-        String imagePath = &quot;./demo.jpg&quot;;
-        String layoutImagePath = &quot;./layout.jpg&quot;;
-
-        File file = new File(imagePath);
-        byte[] fileContent = java.nio.file.Files.readAllBytes(file.toPath());
-        String imageData = Base64.getEncoder().encodeToString(fileContent);
-
-        ObjectMapper objectMapper = new ObjectMapper();
-        ObjectNode params = objectMapper.createObjectNode();
-        params.put(&quot;image&quot;, imageData);
-
-        OkHttpClient client = new OkHttpClient();
-        MediaType JSON = MediaType.Companion.get(&quot;application/json; charset=utf-8&quot;);
-        RequestBody body = RequestBody.Companion.create(params.toString(), JSON);
-        Request request = new Request.Builder()
-                .url(API_URL)
-                .post(body)
-                .build();
-
-        try (Response response = client.newCall(request).execute()) {
-            if (response.isSuccessful()) {
-                String responseBody = response.body().string();
-                JsonNode resultNode = objectMapper.readTree(responseBody);
-                JsonNode result = resultNode.get(&quot;result&quot;);
-                String layoutBase64Image = result.get(&quot;layoutImage&quot;).asText();
-                JsonNode formulas = result.get(&quot;formulas&quot;);
-
-                imageBytes = Base64.getDecoder().decode(layoutBase64Image);
-                try (FileOutputStream fos = new FileOutputStream(layoutImagePath)) {
-                    fos.write(imageBytes);
-                }
-                System.out.println(&quot;Output image saved at &quot; + layoutImagePath);
-
-                System.out.println(&quot;\nDetected formulas: &quot; + formulas.toString());
-            } else {
-                System.err.println(&quot;Request failed with code: &quot; + response.code());
-            }
-        }
-    }
-}
-</code></pre></details>
-
-<details><summary>Go</summary>
-
-<pre><code class="language-go">package main
-
-import (
-    &quot;bytes&quot;
-    &quot;encoding/base64&quot;
-    &quot;encoding/json&quot;
-    &quot;fmt&quot;
-    &quot;io/ioutil&quot;
-    &quot;net/http&quot;
-)
-
-func main() {
-    API_URL := &quot;http://localhost:8080/formula-recognition&quot;
-    imagePath := &quot;./demo.jpg&quot;
-    layoutImagePath := &quot;./layout.jpg&quot;
-
-    imageBytes, err := ioutil.ReadFile(imagePath)
-    if err != nil {
-        fmt.Println(&quot;Error reading image file:&quot;, err)
-        return
-    }
-    imageData := base64.StdEncoding.EncodeToString(imageBytes)
-
-    payload := map[string]string{&quot;image&quot;: imageData}
-    payloadBytes, err := json.Marshal(payload)
-    if err != nil {
-        fmt.Println(&quot;Error marshaling payload:&quot;, err)
-        return
-    }
-
-    client := &amp;http.Client{}
-    req, err := http.NewRequest(&quot;POST&quot;, API_URL, bytes.NewBuffer(payloadBytes))
-    if err != nil {
-        fmt.Println(&quot;Error creating request:&quot;, err)
-        return
-    }
-
-    res, err := client.Do(req)
-    if err != nil {
-        fmt.Println(&quot;Error sending request:&quot;, err)
-        return
-    }
-    defer res.Body.Close()
-
-    body, err := ioutil.ReadAll(res.Body)
-    if err != nil {
-        fmt.Println(&quot;Error reading response body:&quot;, err)
-        return
-    }
-    type Response struct {
-        Result struct {
-            LayoutImage      string   `json:&quot;layoutImage&quot;`
-            Formulas []map[string]interface{} `json:&quot;formulas&quot;`
-        } `json:&quot;result&quot;`
-    }
-    var respData Response
-    err = json.Unmarshal([]byte(string(body)), &amp;respData)
-    if err != nil {
-        fmt.Println(&quot;Error unmarshaling response body:&quot;, err)
-        return
-    }
-
-    layoutImageData, err := base64.StdEncoding.DecodeString(respData.Result.LayoutImage)
-    if err != nil {
-        fmt.Println(&quot;Error decoding base64 image data:&quot;, err)
-        return
-    }
-    err = ioutil.WriteFile(layoutImagePath, layoutImageData, 0644)
-    if err != nil {
-        fmt.Println(&quot;Error writing image to file:&quot;, err)
-        return
-    }
-    fmt.Printf(&quot;Image saved at %s.jpg\n&quot;, layoutImagePath)
-
-    fmt.Println(&quot;\nDetected formulas:&quot;)
-    for _, formula := range respData.Result.Formulas {
-        fmt.Println(formula)
-    }
-}
-</code></pre></details>
-
-<details><summary>C#</summary>
-
-<pre><code class="language-csharp">using System;
-using System.IO;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
-
-class Program
-{
-    static readonly string API_URL = &quot;http://localhost:8080/formula-recognition&quot;;
-    static readonly string imagePath = &quot;./demo.jpg&quot;;
-    static readonly string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    static async Task Main(string[] args)
-    {
-        var httpClient = new HttpClient();
-
-        byte[] imageBytes = File.ReadAllBytes(imagePath);
-        string image_data = Convert.ToBase64String(imageBytes);
-
-        var payload = new JObject{ { &quot;image&quot;, image_data } };
-        var content = new StringContent(payload.ToString(), Encoding.UTF8, &quot;application/json&quot;);
-
-        HttpResponseMessage response = await httpClient.PostAsync(API_URL, content);
-        response.EnsureSuccessStatusCode();
-
-        string responseBody = await response.Content.ReadAsStringAsync();
-        JObject jsonResponse = JObject.Parse(responseBody);
-
-        string layoutBase64Image = jsonResponse[&quot;result&quot;][&quot;layoutImage&quot;].ToString();
-        byte[] layoutImageBytes = Convert.FromBase64String(layoutBase64Image);
-        File.WriteAllBytes(layoutImagePath, layoutImageBytes);
-        Console.WriteLine($&quot;Output image saved at {layoutImagePath}&quot;);
-
-        Console.WriteLine(&quot;\nDetected formulas:&quot;);
-        Console.WriteLine(jsonResponse[&quot;result&quot;][&quot;formulas&quot;].ToString());
-    }
-}
-</code></pre></details>
-
-<details><summary>Node.js</summary>
-
-<pre><code class="language-js">const axios = require('axios');
-const fs = require('fs');
-
-const API_URL = 'http://localhost:8080/formula-recognition'
-const imagePath = './demo.jpg'
-const layoutImagePath = &quot;./layout.jpg&quot;;
-
-let config = {
-   method: 'POST',
-   maxBodyLength: Infinity,
-   url: API_URL,
-   data: JSON.stringify({
-    'image': encodeImageToBase64(imagePath)
-  })
-};
-
-function encodeImageToBase64(filePath) {
-  const bitmap = fs.readFileSync(filePath);
-  return Buffer.from(bitmap).toString('base64');
-}
-
-axios.request(config)
-.then((response) =&gt; {
-    const result = response.data[&quot;result&quot;];
-
-    imageBuffer = Buffer.from(result[&quot;layoutImage&quot;], 'base64');
-    fs.writeFile(layoutImagePath, imageBuffer, (err) =&gt; {
-      if (err) throw err;
-      console.log(`Output image saved at ${layoutImagePath}`);
-    });
-
-    console.log(&quot;\nDetected formulas:&quot;);
-    console.log(result[&quot;formulas&quot;]);
-})
-.catch((error) =&gt; {
-  console.log(error);
-});
-</code></pre></details>
-
-<details><summary>PHP</summary>
-
-<pre><code class="language-php">&lt;?php
-
-$API_URL = &quot;http://localhost:8080/formula-recognition&quot;
-$image_path = &quot;./demo.jpg&quot;;
-$layout_image_path = &quot;./layout.jpg&quot;
-
-$image_data = base64_encode(file_get_contents($image_path));
-$payload = array(&quot;image&quot; =&gt; $image_data);
-
-$ch = curl_init($API_URL);
-curl_setopt($ch, CURLOPT_POST, true);
-curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
-curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-$response = curl_exec($ch);
-curl_close($ch);
-
-$result = json_decode($response, true)[&quot;result&quot;];
-
-file_put_contents($layout_image_path, base64_decode($result[&quot;layoutImage&quot;]));
-echo &quot;Output image saved at &quot; . $layout_image_path . &quot;\n&quot;;
-
-echo &quot;\nDetected formulas:\n&quot;;
-print_r($result[&quot;formulas&quot;]);
-
-?&gt;
+for i, res in enumerate(result[&quot;formulaRecResults&quot;]):
+    print(&quot;Detected formulas:&quot;)
+    print(res[&quot;formulas&quot;])
+    layout_img_path = f&quot;layout_{i}.jpg&quot;
+    with open(layout_img_path, &quot;wb&quot;) as f:
+        f.write(base64.b64decode(res[&quot;layoutImage&quot;]))
+    print(f&quot;Output image saved at {layout_img_path}&quot;)
 </code></pre></details>
 </details>
 <br/>

+ 64 - 382
docs/pipeline_usage/tutorials/ocr_pipelines/formula_recognition.md

@@ -306,6 +306,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -315,9 +320,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -331,6 +340,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>
@@ -362,12 +376,18 @@ for res in output:
 </thead>
 <tbody>
 <tr>
-<td><code>image</code></td>
+<td><code>file</code></td>
 <td><code>string</code></td>
-<td>服务可访问的图像文件的URL或图像文件内容的Base64编码结果。</td>
+<td>服务可访问的图像文件或PDF文件的URL,或上述类型文件内容的Base64编码结果。对于超过10页的PDF文件,只有前10页的内容会被使用。</td>
 <td>是</td>
 </tr>
 <tr>
+<td><code>fileType</code></td>
+<td><code>integer</code></td>
+<td>文件类型。<code>0</code>表示PDF文件,<code>1</code>表示图像文件。若请求体无此属性,则服务将尝试根据URL自动推断文件类型。</td>
+<td>否</td>
+</tr>
+<tr>
 <td><code>inferenceParams</code></td>
 <td><code>object</code></td>
 <td>推理参数。</td>
@@ -407,11 +427,38 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>formulaRecResults</code></td>
+<td><code>object</code></td>
+<td>公式识别结果。数组长度为1(对于图像输入)或文档页数与10中的较小者(对于PDF输入)。对于PDF输入,数组中的每个元素依次表示PDF文件中每一页的处理结果。</td>
+</tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>输入数据信息。</td>
+</tr>
+</tbody>
+</table>
+<p><code>formulaRecResults</code>中的每个元素为一个<code>object</code>,具有如下属性:</p>
+<table>
+<thead>
+<tr>
+<th>名称</th>
+<th>类型</th>
+<th>含义</th>
+</tr>
+</thead>
+<tbody>
+<tr>
 <td><code>formulas</code></td>
 <td><code>array</code></td>
 <td>公式位置和内容。</td>
 </tr>
 <tr>
+<td><code>inputImage</code></td>
+<td><code>string</code></td>
+<td>输入图像。图像为JPEG格式,使用Base64编码。</td>
+</tr>
+<tr>
 <td><code>layoutImage</code></td>
 <td><code>string</code></td>
 <td>版面区域检测结果图。图像为JPEG格式,使用Base64编码。</td>
@@ -445,35 +492,7 @@ for res in output:
 </tr>
 </tbody>
 </table>
-<p><code>result</code>示例如下:</p>
-<pre><code class="language-json">{
-&quot;formulas&quot;: [
-{
-&quot;poly&quot;: [
-[
-444.0,
-244.0
-],
-[
-705.4,
-244.5
-],
-[
-705.8,
-311.3
-],
-[
-444.1,
-311.0
-]
-],
-&quot;latex&quot;: &quot;F({\bf x})=C(F_{1}(x_{1}),\cdot\cdot\cdot,F_{N}(x_{N})).\qquad\qquad\qquad(1)&quot;
-}
-],
-&quot;layoutImage&quot;: &quot;xxxxxx&quot;,
-&quot;ocrImage&quot;: &quot;xxxxxx&quot;
-}
-</code></pre></details>
+</details>
 
 <details><summary>多语言调用服务示例</summary>
 
@@ -484,363 +503,26 @@ for res in output:
 <pre><code class="language-python">import base64
 import requests
 
-API_URL = &quot;http://localhost:8080/formula-recognition&quot; # 服务URL
-image_path = &quot;./demo.jpg&quot;
-layout_image_path = &quot;./layout.jpg&quot;
+API_URL = &quot;http://localhost:8080/formula-recognition&quot;
+file_path = &quot;./demo.jpg&quot;
 
-# 对本地图像进行Base64编码
-with open(image_path, &quot;rb&quot;) as file:
-    image_bytes = file.read()
-    image_data = base64.b64encode(image_bytes).decode(&quot;ascii&quot;)
+with open(file_path, &quot;rb&quot;) as file:
+    file_bytes = file.read()
+    file_data = base64.b64encode(file_bytes).decode(&quot;ascii&quot;)
 
-payload = {&quot;image&quot;: image_data}  # Base64编码的文件内容或者图像URL
+payload = {&quot;file&quot;: file_data, &quot;fileType&quot;: 1}
 
-# 调用API
 response = requests.post(API_URL, json=payload)
 
-# 处理接口返回数据
 assert response.status_code == 200
 result = response.json()[&quot;result&quot;]
-with open(layout_image_path, &quot;wb&quot;) as file:
-    file.write(base64.b64decode(result[&quot;layoutImage&quot;]))
-print(f&quot;Output image saved at {layout_image_path}&quot;)
-print(&quot;\nDetected formulas:&quot;)
-print(result[&quot;formulas&quot;])
-</code></pre></details>
-
-<details><summary>C++</summary>
-
-<pre><code class="language-cpp">#include &lt;iostream&gt;
-#include &quot;cpp-httplib/httplib.h&quot; // https://github.com/Huiyicc/cpp-httplib
-#include &quot;nlohmann/json.hpp&quot; // https://github.com/nlohmann/json
-#include &quot;base64.hpp&quot; // https://github.com/tobiaslocker/base64
-
-int main() {
-    httplib::Client client(&quot;localhost:8080&quot;);
-    const std::string imagePath = &quot;./demo.jpg&quot;;
-    const std::string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    httplib::Headers headers = {
-        {&quot;Content-Type&quot;, &quot;application/json&quot;}
-    };
-
-    // 对本地图像进行Base64编码
-    std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
-    std::streamsize size = file.tellg();
-    file.seekg(0, std::ios::beg);
-
-    std::vector&lt;char&gt; buffer(size);
-    if (!file.read(buffer.data(), size)) {
-        std::cerr &lt;&lt; &quot;Error reading file.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-    std::string bufferStr(reinterpret_cast&lt;const char*&gt;(buffer.data()), buffer.size());
-    std::string encodedImage = base64::to_base64(bufferStr);
-
-    nlohmann::json jsonObj;
-    jsonObj[&quot;image&quot;] = encodedImage;
-    std::string body = jsonObj.dump();
-
-    // 调用API
-    auto response = client.Post(&quot;/formula-recognition&quot;, headers, body, &quot;application/json&quot;);
-    // 处理接口返回数据
-    if (response &amp;&amp; response-&gt;status == 200) {
-        nlohmann::json jsonResponse = nlohmann::json::parse(response-&gt;body);
-        auto result = jsonResponse[&quot;result&quot;];
-
-        encodedImage = result[&quot;layoutImage&quot;];
-        decodedString = base64::from_base64(encodedImage);
-        std::vector&lt;unsigned char&gt; decodedLayoutImage(decodedString.begin(), decodedString.end());
-        std::ofstream outputLayoutFile(layoutImagePath, std::ios::binary | std::ios::out);
-        if (outputLayoutFile.is_open()) {
-            outputLayoutFile.write(reinterpret_cast&lt;char*&gt;(decodedLayoutImage.data()), decodedLayoutImage.size());
-            outputLayoutFile.close();
-            std::cout &lt;&lt; &quot;Output image saved at &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        } else {
-            std::cerr &lt;&lt; &quot;Unable to open file for writing: &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        }
-
-        auto formulas = result[&quot;formulas&quot;];
-        std::cout &lt;&lt; &quot;\nDetected formulas:&quot; &lt;&lt; std::endl;
-        for (const auto&amp; formula : formulas) {
-            std::cout &lt;&lt; formula &lt;&lt; std::endl;
-        }
-    } else {
-        std::cout &lt;&lt; &quot;Failed to send HTTP request.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-
-    return 0;
-}
-</code></pre></details>
-
-<details><summary>Java</summary>
-
-<pre><code class="language-java">import okhttp3.*;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Base64;
-
-public class Main {
-    public static void main(String[] args) throws IOException {
-        String API_URL = &quot;http://localhost:8080/formula-recognition&quot;; // 服务URL
-        String imagePath = &quot;./demo.jpg&quot;; // 本地图像
-        String layoutImagePath = &quot;./layout.jpg&quot;;
-
-        // 对本地图像进行Base64编码
-        File file = new File(imagePath);
-        byte[] fileContent = java.nio.file.Files.readAllBytes(file.toPath());
-        String imageData = Base64.getEncoder().encodeToString(fileContent);
-
-        ObjectMapper objectMapper = new ObjectMapper();
-        ObjectNode params = objectMapper.createObjectNode();
-        params.put(&quot;image&quot;, imageData); // Base64编码的文件内容或者图像URL
-
-        // 创建 OkHttpClient 实例
-        OkHttpClient client = new OkHttpClient();
-        MediaType JSON = MediaType.Companion.get(&quot;application/json; charset=utf-8&quot;);
-        RequestBody body = RequestBody.Companion.create(params.toString(), JSON);
-        Request request = new Request.Builder()
-                .url(API_URL)
-                .post(body)
-                .build();
-
-        // 调用API并处理接口返回数据
-        try (Response response = client.newCall(request).execute()) {
-            if (response.isSuccessful()) {
-                String responseBody = response.body().string();
-                JsonNode resultNode = objectMapper.readTree(responseBody);
-                JsonNode result = resultNode.get(&quot;result&quot;);
-                String layoutBase64Image = result.get(&quot;layoutImage&quot;).asText();
-                JsonNode formulas = result.get(&quot;formulas&quot;);
-
-                imageBytes = Base64.getDecoder().decode(layoutBase64Image);
-                try (FileOutputStream fos = new FileOutputStream(layoutImagePath)) {
-                    fos.write(imageBytes);
-                }
-                System.out.println(&quot;Output image saved at &quot; + layoutImagePath);
-
-                System.out.println(&quot;\nDetected formulas: &quot; + formulas.toString());
-            } else {
-                System.err.println(&quot;Request failed with code: &quot; + response.code());
-            }
-        }
-    }
-}
-</code></pre></details>
-
-<details><summary>Go</summary>
-
-<pre><code class="language-go">package main
-
-import (
-    &quot;bytes&quot;
-    &quot;encoding/base64&quot;
-    &quot;encoding/json&quot;
-    &quot;fmt&quot;
-    &quot;io/ioutil&quot;
-    &quot;net/http&quot;
-)
-
-func main() {
-    API_URL := &quot;http://localhost:8080/formula-recognition&quot;
-    imagePath := &quot;./demo.jpg&quot;
-    layoutImagePath := &quot;./layout.jpg&quot;
-
-    // 对本地图像进行Base64编码
-    imageBytes, err := ioutil.ReadFile(imagePath)
-    if err != nil {
-        fmt.Println(&quot;Error reading image file:&quot;, err)
-        return
-    }
-    imageData := base64.StdEncoding.EncodeToString(imageBytes)
-
-    payload := map[string]string{&quot;image&quot;: imageData} // Base64编码的文件内容或者图像URL
-    payloadBytes, err := json.Marshal(payload)
-    if err != nil {
-        fmt.Println(&quot;Error marshaling payload:&quot;, err)
-        return
-    }
-
-    // 调用API
-    client := &amp;http.Client{}
-    req, err := http.NewRequest(&quot;POST&quot;, API_URL, bytes.NewBuffer(payloadBytes))
-    if err != nil {
-        fmt.Println(&quot;Error creating request:&quot;, err)
-        return
-    }
-
-    res, err := client.Do(req)
-    if err != nil {
-        fmt.Println(&quot;Error sending request:&quot;, err)
-        return
-    }
-    defer res.Body.Close()
-
-    // 处理接口返回数据
-    body, err := ioutil.ReadAll(res.Body)
-    if err != nil {
-        fmt.Println(&quot;Error reading response body:&quot;, err)
-        return
-    }
-    type Response struct {
-        Result struct {
-            LayoutImage      string   `json:&quot;layoutImage&quot;`
-            Formulas []map[string]interface{} `json:&quot;formulas&quot;`
-        } `json:&quot;result&quot;`
-    }
-    var respData Response
-    err = json.Unmarshal([]byte(string(body)), &amp;respData)
-    if err != nil {
-        fmt.Println(&quot;Error unmarshaling response body:&quot;, err)
-        return
-    }
-
-    layoutImageData, err := base64.StdEncoding.DecodeString(respData.Result.LayoutImage)
-    if err != nil {
-        fmt.Println(&quot;Error decoding base64 image data:&quot;, err)
-        return
-    }
-    err = ioutil.WriteFile(layoutImagePath, layoutImageData, 0644)
-    if err != nil {
-        fmt.Println(&quot;Error writing image to file:&quot;, err)
-        return
-    }
-    fmt.Printf(&quot;Image saved at %s.jpg\n&quot;, layoutImagePath)
-
-    fmt.Println(&quot;\nDetected formulas:&quot;)
-    for _, formula := range respData.Result.Formulas {
-        fmt.Println(formula)
-    }
-}
-</code></pre></details>
-
-<details><summary>C#</summary>
-
-<pre><code class="language-csharp">using System;
-using System.IO;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
-
-class Program
-{
-    static readonly string API_URL = &quot;http://localhost:8080/formula-recognition&quot;;
-    static readonly string imagePath = &quot;./demo.jpg&quot;;
-    static readonly string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    static async Task Main(string[] args)
-    {
-        var httpClient = new HttpClient();
-
-        // 对本地图像进行Base64编码
-        byte[] imageBytes = File.ReadAllBytes(imagePath);
-        string image_data = Convert.ToBase64String(imageBytes);
-
-        var payload = new JObject{ { &quot;image&quot;, image_data } }; // Base64编码的文件内容或者图像URL
-        var content = new StringContent(payload.ToString(), Encoding.UTF8, &quot;application/json&quot;);
-
-        // 调用API
-        HttpResponseMessage response = await httpClient.PostAsync(API_URL, content);
-        response.EnsureSuccessStatusCode();
-
-        // 处理接口返回数据
-        string responseBody = await response.Content.ReadAsStringAsync();
-        JObject jsonResponse = JObject.Parse(responseBody);
-
-        string layoutBase64Image = jsonResponse[&quot;result&quot;][&quot;layoutImage&quot;].ToString();
-        byte[] layoutImageBytes = Convert.FromBase64String(layoutBase64Image);
-        File.WriteAllBytes(layoutImagePath, layoutImageBytes);
-        Console.WriteLine($&quot;Output image saved at {layoutImagePath}&quot;);
-
-        Console.WriteLine(&quot;\nDetected formulas:&quot;);
-        Console.WriteLine(jsonResponse[&quot;result&quot;][&quot;formulas&quot;].ToString());
-    }
-}
-</code></pre></details>
-
-<details><summary>Node.js</summary>
-
-<pre><code class="language-js">const axios = require('axios');
-const fs = require('fs');
-
-const API_URL = 'http://localhost:8080/formula-recognition'
-const imagePath = './demo.jpg'
-const layoutImagePath = &quot;./layout.jpg&quot;;
-
-let config = {
-   method: 'POST',
-   maxBodyLength: Infinity,
-   url: API_URL,
-   data: JSON.stringify({
-    'image': encodeImageToBase64(imagePath)  // Base64编码的文件内容或者图像URL
-  })
-};
-
-// 对本地图像进行Base64编码
-function encodeImageToBase64(filePath) {
-  const bitmap = fs.readFileSync(filePath);
-  return Buffer.from(bitmap).toString('base64');
-}
-
-// 调用API
-axios.request(config)
-.then((response) =&gt; {
-    // 处理接口返回数据
-    const result = response.data[&quot;result&quot;];
-
-    imageBuffer = Buffer.from(result[&quot;layoutImage&quot;], 'base64');
-    fs.writeFile(layoutImagePath, imageBuffer, (err) =&gt; {
-      if (err) throw err;
-      console.log(`Output image saved at ${layoutImagePath}`);
-    });
-
-    console.log(&quot;\nDetected formulas:&quot;);
-    console.log(result[&quot;formulas&quot;]);
-})
-.catch((error) =&gt; {
-  console.log(error);
-});
-</code></pre></details>
-
-<details><summary>PHP</summary>
-
-<pre><code class="language-php">&lt;?php
-
-$API_URL = &quot;http://localhost:8080/formula-recognition&quot;; // 服务URL
-$image_path = &quot;./demo.jpg&quot;;
-$layout_image_path = &quot;./layout.jpg&quot;;
-
-// 对本地图像进行Base64编码
-$image_data = base64_encode(file_get_contents($image_path));
-$payload = array(&quot;image&quot; =&gt; $image_data); // Base64编码的文件内容或者图像URL
-
-// 调用API
-$ch = curl_init($API_URL);
-curl_setopt($ch, CURLOPT_POST, true);
-curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
-curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-$response = curl_exec($ch);
-curl_close($ch);
-
-// 处理接口返回数据
-$result = json_decode($response, true)[&quot;result&quot;];
-
-file_put_contents($layout_image_path, base64_decode($result[&quot;layoutImage&quot;]));
-echo &quot;Output image saved at &quot; . $layout_image_path . &quot;\n&quot;;
-
-echo &quot;\nDetected formulas:\n&quot;;
-print_r($result[&quot;formulas&quot;]);
-
-?&gt;
+for i, res in enumerate(result[&quot;formulaRecResults&quot;]):
+    print(&quot;Detected formulas:&quot;)
+    print(res[&quot;formulas&quot;])
+    layout_img_path = f&quot;layout_{i}.jpg&quot;
+    with open(layout_img_path, &quot;wb&quot;) as f:
+        f.write(base64.b64decode(res[&quot;layoutImage&quot;]))
+    print(f&quot;Output image saved at {layout_img_path}&quot;)
 </code></pre></details>
 </details>
 <br/>

+ 32 - 16
docs/pipeline_usage/tutorials/ocr_pipelines/layout_parsing.en.md

@@ -541,7 +541,7 @@ Below are the API references and multi-language service invocation examples:
 <ul>
 <li>The HTTP request method is POST.</li>
 <li>The request body and the response body are both JSON data (JSON objects).</li>
-<li>When the request is processed successfully, the response status code is <code>200</code>, and the response body attributes are as follows:</li>
+<li>When the request is processed successfully, the response status code is <code>200</code>, and the response body properties are as follows:</li>
 </ul>
 <table>
 <thead>
@@ -553,6 +553,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -562,11 +567,15 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> attribute, of type <code>object</code>, which stores the operation result information.</p>
 <ul>
-<li>When the request is not processed successfully, the response body attributes are as follows:</li>
+<li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
 <table>
 <thead>
@@ -578,6 +587,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>
@@ -596,7 +610,7 @@ Below are the API references and multi-language service invocation examples:
 <p>Performs layout parsing.</p>
 <p><code>POST /layout-parsing</code></p>
 <ul>
-<li>Request body attributes:</li>
+<li>Request body properties:</li>
 </ul>
 <table>
 <thead>
@@ -617,7 +631,7 @@ Below are the API references and multi-language service invocation examples:
 <tr>
 <td><code>fileType</code></td>
 <td><code>integer</code></td>
-<td>File type. <code>0</code> indicates a PDF file, <code>1</code> indicates an image file. If this attribute is not present in the request body, the service will attempt to infer the file type automatically based on the URL.</td>
+<td>File type. <code>0</code> indicates a PDF file, and <code>1</code> indicates an image file. If this property is not present in the request body, the service will attempt to infer the file type automatically based on the URL.</td>
 <td>No</td>
 </tr>
 <tr>
@@ -627,7 +641,7 @@ Below are the API references and multi-language service invocation examples:
 <td>No</td>
 </tr>
 <tr>
-<td><code>useImgUnwrapping</code></td>
+<td><code>useImgUnwarping</code></td>
 <td><code>boolean</code></td>
 <td>Whether to enable text image rectification. This function is enabled by default.</td>
 <td>No</td>
@@ -646,7 +660,7 @@ Below are the API references and multi-language service invocation examples:
 </tr>
 </tbody>
 </table>
-<p>Attributes of <code>inferenceParams</code>:</p>
+<p>properties of <code>inferenceParams</code>:</p>
 <table>
 <thead>
 <tr>
@@ -666,7 +680,7 @@ Below are the API references and multi-language service invocation examples:
 </tbody>
 </table>
 <ul>
-<li>When the request is processed successfully, the <code>result</code> of the response body has the following attributes:</li>
+<li>When the request is processed successfully, the <code>result</code> of the response body has the following properties:</li>
 </ul>
 <table>
 <thead>
@@ -682,9 +696,14 @@ Below are the API references and multi-language service invocation examples:
 <td><code>array</code></td>
 <td>Layout parsing results. The array length is 1 (for image input) or the smaller of the number of document pages and 10 (for PDF input). For PDF input, each element in the array represents the processing result of each page in the PDF file.</td>
 </tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>Information about the input data.</td>
+</tr>
 </tbody>
 </table>
-<p>Each element in <code>layoutParsingResults</code> is an <code>object</code> with the following attributes:</p>
+<p>Each element in <code>layoutParsingResults</code> is an <code>object</code> with the following properties:</p>
 <table>
 <thead>
 <tr>
@@ -701,7 +720,7 @@ Below are the API references and multi-language service invocation examples:
 </tr>
 </tbody>
 </table>
-<p>Each element in <code>layoutElements</code> is an <code>object</code> with the following attributes:</p>
+<p>Each element in <code>layoutElements</code> is an <code>object</code> with the following properties:</p>
 <table>
 <thead>
 <tr>
@@ -749,25 +768,22 @@ Below are the API references and multi-language service invocation examples:
 <pre><code class="language-python">import base64
 import requests
 
-API_URL = &quot;http://localhost:8080/layout-parsing&quot; # 服务URL
+API_URL = &quot;http://localhost:8080/layout-parsing&quot;
 
-# 对本地图像进行Base64编码
 with open(image_path, &quot;rb&quot;) as file:
     image_bytes = file.read()
     image_data = base64.b64encode(image_bytes).decode(&quot;ascii&quot;)
 
 payload = {
-    &quot;file&quot;: image_data, # Base64编码的文件内容或者文件URL
+    &quot;file&quot;: image_data,
     &quot;fileType&quot;: 1,
     &quot;useImgOrientationCls&quot;: True,
-    &quot;useImgUnwrapping&quot;: True,
+    &quot;useImgUnwarping&quot;: True,
     &quot;useSealTextDet&quot;: True,
 }
 
-# 调用API
 response = requests.post(API_URL, json=payload)
 
-# 处理接口返回数据
 assert response.status_code == 200
 result = response.json()[&quot;result&quot;]
 print(&quot;\nDetected layout elements:&quot;)

+ 22 - 3
docs/pipeline_usage/tutorials/ocr_pipelines/layout_parsing.md

@@ -556,6 +556,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -565,9 +570,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -581,6 +590,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>
@@ -630,7 +644,7 @@ for res in output:
 <td>否</td>
 </tr>
 <tr>
-<td><code>useImgUnwrapping</code></td>
+<td><code>useImgUnwarping</code></td>
 <td><code>boolean</code></td>
 <td>是否启用文本图像矫正功能。默认启用该功能。</td>
 <td>否</td>
@@ -685,6 +699,11 @@ for res in output:
 <td><code>array</code></td>
 <td>版面解析结果。数组长度为1(对于图像输入)或文档页数与10中的较小者(对于PDF输入)。对于PDF输入,数组中的每个元素依次表示PDF文件中每一页的处理结果。</td>
 </tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>输入数据信息。</td>
+</tr>
 </tbody>
 </table>
 <p><code>layoutParsingResults</code>中的每个元素为一个<code>object</code>,具有如下属性:</p>
@@ -764,7 +783,7 @@ payload = {
     &quot;file&quot;: image_data, # Base64编码的文件内容或者文件URL
     &quot;fileType&quot;: 1,
     &quot;useImgOrientationCls&quot;: True,
-    &quot;useImgUnwrapping&quot;: True,
+    &quot;useImgUnwarping&quot;: True,
     &quot;useSealTextDet&quot;: True,
 }
 

+ 67 - 388
docs/pipeline_usage/tutorials/ocr_pipelines/seal_recognition.en.md

@@ -484,6 +484,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -493,9 +498,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -509,6 +518,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>
@@ -540,12 +554,18 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
-<td><code>image</code></td>
+<td><code>file</code></td>
 <td><code>string</code></td>
-<td>The URL of an image file accessible by the service or the Base64 encoded result of the image file content.</td>
+<td>The URL of an image file or PDF file accessible by the service, or the Base64 encoded result of the content of the above-mentioned file types. For PDF files with more than 10 pages, only the content of the first 10 pages will be used.</td>
 <td>Yes</td>
 </tr>
 <tr>
+<td><code>fileType</code></td>
+<td><code>integer</code></td>
+<td>File type. <code>0</code> indicates a PDF file, and <code>1</code> indicates an image file. If this property is not present in the request body, the service will attempt to infer the file type automatically based on the URL.</td>
+<td>No</td>
+</tr>
+<tr>
 <td><code>inferenceParams</code></td>
 <td><code>object</code></td>
 <td>Inference parameters.</td>
@@ -567,13 +587,13 @@ Below are the API references and multi-language service invocation examples:
 <tr>
 <td><code>maxLongSide</code></td>
 <td><code>integer</code></td>
-<td>During inference, if the length of the longer side of the input image for the text detection model is greater than <code>maxLongSide</code>, the image will be scaled so that the length of the longer side equals <code>maxLongSide</code>.</td>
+<td>During inference, if the length of the longer side of the input image for the layout detection model is greater than <code>maxLongSide</code>, the image will be scaled so that the length of the longer side equals <code>maxLongSide</code>.</td>
 <td>No</td>
 </tr>
 </tbody>
 </table>
 <ul>
-<li>When the request is processed successfully, the <code>result</code> of the response body has the following properties:</li>
+<li>When the request is processed successfully, the <code>result</code> in the response body has the following properties:</li>
 </ul>
 <table>
 <thead>
@@ -585,11 +605,38 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>sealRecResults</code></td>
+<td><code>array</code></td>
+<td>Seal recognition results. The array length is 1 (for image input) or the smaller of the number of document pages and 10 (for PDF input). For PDF input, each element in the array represents the processing result of each page in the PDF file.</td>
+</tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>Information about the input data.</td>
+</tr>
+</tbody>
+</table>
+<p>Each element in <code>sealRecResults</code> is an <code>object</code> with the following properties:</p>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Type</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
 <td><code>texts</code></td>
 <td><code>array</code></td>
 <td>Positions, contents, and scores of texts.</td>
 </tr>
 <tr>
+<td><code>inputImage</code></td>
+<td><code>string</code></td>
+<td>Input image. The image is in JPEG format and encoded using Base64.</td>
+</tr>
+<tr>
 <td><code>layoutImage</code></td>
 <td><code>string</code></td>
 <td>Layout area detection result image. The image is in JPEG format and encoded using Base64.</td>
@@ -639,396 +686,28 @@ Below are the API references and multi-language service invocation examples:
 import requests
 
 API_URL = &quot;http://localhost:8080/seal-recognition&quot;
-image_path = &quot;./demo.jpg&quot;
-ocr_image_path = &quot;./ocr.jpg&quot;
-layout_image_path = &quot;./layout.jpg&quot;
+file_path = &quot;./demo.jpg&quot;
 
-with open(image_path, &quot;rb&quot;) as file:
-    image_bytes = file.read()
-    image_data = base64.b64encode(image_bytes).decode(&quot;ascii&quot;)
+with open(file_path, &quot;rb&quot;) as file:
+    file_bytes = file.read()
+    file_data = base64.b64encode(file_bytes).decode(&quot;ascii&quot;)
 
-payload = {&quot;image&quot;: image_data}
+payload = {&quot;file&quot;: file_data, &quot;fileType&quot;: 1}
 
 response = requests.post(API_URL, json=payload)
 
 assert response.status_code == 200
 result = response.json()[&quot;result&quot;]
-with open(ocr_image_path, &quot;wb&quot;) as file:
-    file.write(base64.b64decode(result[&quot;ocrImage&quot;]))
-print(f&quot;Output image saved at {ocr_image_path}&quot;)
-with open(layout_image_path, &quot;wb&quot;) as file:
-    file.write(base64.b64decode(result[&quot;layoutImage&quot;]))
-print(f&quot;Output image saved at {layout_image_path}&quot;)
-print(&quot;\nDetected texts:&quot;)
-print(result[&quot;texts&quot;])
-</code></pre></details>
-
-<details><summary>C++</summary>
-
-<pre><code class="language-cpp">#include &lt;iostream&gt;
-#include &quot;cpp-httplib/httplib.h&quot; // https://github.com/Huiyicc/cpp-httplib
-#include &quot;nlohmann/json.hpp&quot; // https://github.com/nlohmann/json
-#include &quot;base64.hpp&quot; // https://github.com/tobiaslocker/base64
-
-int main() {
-    httplib::Client client(&quot;localhost:8080&quot;);
-    const std::string imagePath = &quot;./demo.jpg&quot;;
-    const std::string ocrImagePath = &quot;./ocr.jpg&quot;;
-    const std::string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    httplib::Headers headers = {
-        {&quot;Content-Type&quot;, &quot;application/json&quot;}
-    };
-
-    std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
-    std::streamsize size = file.tellg();
-    file.seekg(0, std::ios::beg);
-
-    std::vector&lt;char&gt; buffer(size);
-    if (!file.read(buffer.data(), size)) {
-        std::cerr &lt;&lt; &quot;Error reading file.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-    std::string bufferStr(reinterpret_cast&lt;const char*&gt;(buffer.data()), buffer.size());
-    std::string encodedImage = base64::to_base64(bufferStr);
-
-    nlohmann::json jsonObj;
-    jsonObj[&quot;image&quot;] = encodedImage;
-    std::string body = jsonObj.dump();
-
-    auto response = client.Post(&quot;/seal-recognition&quot;, headers, body, &quot;application/json&quot;);
-    if (response &amp;&amp; response-&gt;status == 200) {
-        nlohmann::json jsonResponse = nlohmann::json::parse(response-&gt;body);
-        auto result = jsonResponse[&quot;result&quot;];
-
-        encodedImage = result[&quot;ocrImage&quot;];
-        std::string decoded_string = base64::from_base64(encodedImage);
-        std::vector&lt;unsigned char&gt; decodedOcrImage(decoded_string.begin(), decoded_string.end());
-        std::ofstream outputOcrFile(ocrImagePath, std::ios::binary | std::ios::out);
-        if (outputOcrFile.is_open()) {
-            outputOcrFile.write(reinterpret_cast&lt;char*&gt;(decodedOcrImage.data()), decodedOcrImage.size());
-            outputOcrFile.close();
-            std::cout &lt;&lt; &quot;Output image saved at &quot; &lt;&lt; ocrImagePath &lt;&lt; std::endl;
-        } else {
-            std::cerr &lt;&lt; &quot;Unable to open file for writing: &quot; &lt;&lt; ocrImagePath &lt;&lt; std::endl;
-        }
-
-        encodedImage = result[&quot;layoutImage&quot;];
-        decodedString = base64::from_base64(encodedImage);
-        std::vector&lt;unsigned char&gt; decodedLayoutImage(decodedString.begin(), decodedString.end());
-        std::ofstream outputLayoutFile(layoutImagePath, std::ios::binary | std::ios::out);
-        if (outputLayoutFile.is_open()) {
-            outputLayoutFile.write(reinterpret_cast&lt;char*&gt;(decodedLayoutImage.data()), decodedLayoutImage.size());
-            outputLayoutFile.close();
-            std::cout &lt;&lt; &quot;Output image saved at &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        } else {
-            std::cerr &lt;&lt; &quot;Unable to open file for writing: &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        }
-
-        auto texts = result[&quot;texts&quot;];
-        std::cout &lt;&lt; &quot;\nDetected texts:&quot; &lt;&lt; std::endl;
-        for (const auto&amp; text : texts) {
-            std::cout &lt;&lt; text &lt;&lt; std::endl;
-        }
-    } else {
-        std::cout &lt;&lt; &quot;Failed to send HTTP request.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-
-    return 0;
-}
-</code></pre></details>
-
-<details><summary>Java</summary>
-
-<pre><code class="language-java">import okhttp3.*;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Base64;
-
-public class Main {
-    public static void main(String[] args) throws IOException {
-        String API_URL = &quot;http://localhost:8080/seal-recognition&quot;;
-        String imagePath = &quot;./demo.jpg&quot;;
-        String ocrImagePath = &quot;./ocr.jpg&quot;;
-        String layoutImagePath = &quot;./layout.jpg&quot;;
-
-        File file = new File(imagePath);
-        byte[] fileContent = java.nio.file.Files.readAllBytes(file.toPath());
-        String imageData = Base64.getEncoder().encodeToString(fileContent);
-
-        ObjectMapper objectMapper = new ObjectMapper();
-        ObjectNode params = objectMapper.createObjectNode();
-        params.put(&quot;image&quot;, imageData);
-
-        OkHttpClient client = new OkHttpClient();
-        MediaType JSON = MediaType.Companion.get(&quot;application/json; charset=utf-8&quot;);
-        RequestBody body = RequestBody.Companion.create(params.toString(), JSON);
-        Request request = new Request.Builder()
-                .url(API_URL)
-                .post(body)
-                .build();
-
-        try (Response response = client.newCall(request).execute()) {
-            if (response.isSuccessful()) {
-                String responseBody = response.body().string();
-                JsonNode resultNode = objectMapper.readTree(responseBody);
-                JsonNode result = resultNode.get(&quot;result&quot;);
-                String ocrBase64Image = result.get(&quot;ocrImage&quot;).asText();
-                String layoutBase64Image = result.get(&quot;layoutImage&quot;).asText();
-                JsonNode texts = result.get(&quot;texts&quot;);
-
-                byte[] imageBytes = Base64.getDecoder().decode(ocrBase64Image);
-                try (FileOutputStream fos = new FileOutputStream(ocrImagePath)) {
-                    fos.write(imageBytes);
-                }
-                System.out.println(&quot;Output image saved at &quot; + ocrBase64Image);
-
-                imageBytes = Base64.getDecoder().decode(layoutBase64Image);
-                try (FileOutputStream fos = new FileOutputStream(layoutImagePath)) {
-                    fos.write(imageBytes);
-                }
-                System.out.println(&quot;Output image saved at &quot; + layoutImagePath);
-
-                System.out.println(&quot;\nDetected texts: &quot; + texts.toString());
-            } else {
-                System.err.println(&quot;Request failed with code: &quot; + response.code());
-            }
-        }
-    }
-}
-</code></pre></details>
-
-<details><summary>Go</summary>
-
-<pre><code class="language-go">package main
-
-import (
-    &quot;bytes&quot;
-    &quot;encoding/base64&quot;
-    &quot;encoding/json&quot;
-    &quot;fmt&quot;
-    &quot;io/ioutil&quot;
-    &quot;net/http&quot;
-)
-
-func main() {
-    API_URL := &quot;http://localhost:8080/seal-recognition&quot;
-    imagePath := &quot;./demo.jpg&quot;
-    ocrImagePath := &quot;./ocr.jpg&quot;
-    layoutImagePath := &quot;./layout.jpg&quot;
-
-    imageBytes, err := ioutil.ReadFile(imagePath)
-    if err != nil {
-        fmt.Println(&quot;Error reading image file:&quot;, err)
-        return
-    }
-    imageData := base64.StdEncoding.EncodeToString(imageBytes)
-
-    payload := map[string]string{&quot;image&quot;: imageData}
-    payloadBytes, err := json.Marshal(payload)
-    if err != nil {
-        fmt.Println(&quot;Error marshaling payload:&quot;, err)
-        return
-    }
-
-    client := &amp;http.Client{}
-    req, err := http.NewRequest(&quot;POST&quot;, API_URL, bytes.NewBuffer(payloadBytes))
-    if err != nil {
-        fmt.Println(&quot;Error creating request:&quot;, err)
-        return
-    }
-
-    res, err := client.Do(req)
-    if err != nil {
-        fmt.Println(&quot;Error sending request:&quot;, err)
-        return
-    }
-    defer res.Body.Close()
-
-    body, err := ioutil.ReadAll(res.Body)
-    if err != nil {
-        fmt.Println(&quot;Error reading response body:&quot;, err)
-        return
-    }
-    type Response struct {
-        Result struct {
-            OcrImage      string   `json:&quot;ocrImage&quot;`
-            LayoutImage      string   `json:&quot;layoutImage&quot;`
-            Texts []map[string]interface{} `json:&quot;texts&quot;`
-        } `json:&quot;result&quot;`
-    }
-    var respData Response
-    err = json.Unmarshal([]byte(string(body)), &amp;respData)
-    if err != nil {
-        fmt.Println(&quot;Error unmarshaling response body:&quot;, err)
-        return
-    }
-
-    ocrImageData, err := base64.StdEncoding.DecodeString(respData.Result.OcrImage)
-    if err != nil {
-        fmt.Println(&quot;Error decoding base64 image data:&quot;, err)
-        return
-    }
-    err = ioutil.WriteFile(ocrImagePath, ocrImageData, 0644)
-    if err != nil {
-        fmt.Println(&quot;Error writing image to file:&quot;, err)
-        return
-    }
-    fmt.Printf(&quot;Image saved at %s.jpg\n&quot;, ocrImagePath)
-
-    layoutImageData, err := base64.StdEncoding.DecodeString(respData.Result.LayoutImage)
-    if err != nil {
-        fmt.Println(&quot;Error decoding base64 image data:&quot;, err)
-        return
-    }
-    err = ioutil.WriteFile(layoutImagePath, layoutImageData, 0644)
-    if err != nil {
-        fmt.Println(&quot;Error writing image to file:&quot;, err)
-        return
-    }
-    fmt.Printf(&quot;Image saved at %s.jpg\n&quot;, layoutImagePath)
-
-    fmt.Println(&quot;\nDetected texts:&quot;)
-    for _, text := range respData.Result.Texts {
-        fmt.Println(text)
-    }
-}
-</code></pre></details>
-
-<details><summary>C#</summary>
-
-<pre><code class="language-csharp">using System;
-using System.IO;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
-
-class Program
-{
-    static readonly string API_URL = &quot;http://localhost:8080/seal-recognition&quot;;
-    static readonly string imagePath = &quot;./demo.jpg&quot;;
-    static readonly string ocrImagePath = &quot;./ocr.jpg&quot;;
-    static readonly string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    static async Task Main(string[] args)
-    {
-        var httpClient = new HttpClient();
-
-        byte[] imageBytes = File.ReadAllBytes(imagePath);
-        string image_data = Convert.ToBase64String(imageBytes);
-
-        var payload = new JObject{ { &quot;image&quot;, image_data } };
-        var content = new StringContent(payload.ToString(), Encoding.UTF8, &quot;application/json&quot;);
-
-        HttpResponseMessage response = await httpClient.PostAsync(API_URL, content);
-        response.EnsureSuccessStatusCode();
-
-        string responseBody = await response.Content.ReadAsStringAsync();
-        JObject jsonResponse = JObject.Parse(responseBody);
-
-        string ocrBase64Image = jsonResponse[&quot;result&quot;][&quot;ocrImage&quot;].ToString();
-        byte[] ocrImageBytes = Convert.FromBase64String(ocrBase64Image);
-        File.WriteAllBytes(ocrImagePath, ocrImageBytes);
-        Console.WriteLine($&quot;Output image saved at {ocrImagePath}&quot;);
-
-        string layoutBase64Image = jsonResponse[&quot;result&quot;][&quot;layoutImage&quot;].ToString();
-        byte[] layoutImageBytes = Convert.FromBase64String(layoutBase64Image);
-        File.WriteAllBytes(layoutImagePath, layoutImageBytes);
-        Console.WriteLine($&quot;Output image saved at {layoutImagePath}&quot;);
-
-        Console.WriteLine(&quot;\nDetected texts:&quot;);
-        Console.WriteLine(jsonResponse[&quot;result&quot;][&quot;texts&quot;].ToString());
-    }
-}
-</code></pre></details>
-
-<details><summary>Node.js</summary>
-
-<pre><code class="language-js">const axios = require('axios');
-const fs = require('fs');
-
-const API_URL = 'http://localhost:8080/seal-recognition'
-const imagePath = './demo.jpg'
-const ocrImagePath = &quot;./ocr.jpg&quot;;
-const layoutImagePath = &quot;./layout.jpg&quot;;
-
-let config = {
-   method: 'POST',
-   maxBodyLength: Infinity,
-   url: API_URL,
-   data: JSON.stringify({
-    'image': encodeImageToBase64(imagePath)
-  })
-};
-
-function encodeImageToBase64(filePath) {
-  const bitmap = fs.readFileSync(filePath);
-  return Buffer.from(bitmap).toString('base64');
-}
-
-axios.request(config)
-.then((response) =&gt; {
-    const result = response.data[&quot;result&quot;];
-
-    const imageBuffer = Buffer.from(result[&quot;ocrImage&quot;], 'base64');
-    fs.writeFile(ocrImagePath, imageBuffer, (err) =&gt; {
-      if (err) throw err;
-      console.log(`Output image saved at ${ocrImagePath}`);
-    });
-
-    imageBuffer = Buffer.from(result[&quot;layoutImage&quot;], 'base64');
-    fs.writeFile(layoutImagePath, imageBuffer, (err) =&gt; {
-      if (err) throw err;
-      console.log(`Output image saved at ${layoutImagePath}`);
-    });
-
-    console.log(&quot;\nDetected texts:&quot;);
-    console.log(result[&quot;texts&quot;]);
-})
-.catch((error) =&gt; {
-  console.log(error);
-});
-</code></pre></details>
-
-<details><summary>PHP</summary>
-
-<pre><code class="language-php">&lt;?php
-
-$API_URL = &quot;http://localhost:8080/seal-recognition&quot;;
-$image_path = &quot;./demo.jpg&quot;;
-$ocr_image_path = &quot;./ocr.jpg&quot;;
-$layout_image_path = &quot;./layout.jpg&quot;;
-
-$image_data = base64_encode(file_get_contents($image_path));
-$payload = array(&quot;image&quot; =&gt; $image_data);
-
-$ch = curl_init($API_URL);
-curl_setopt($ch, CURLOPT_POST, true);
-curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
-curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-$response = curl_exec($ch);
-curl_close($ch);
-
-$result = json_decode($response, true)[&quot;result&quot;];
-file_put_contents($ocr_image_path, base64_decode($result[&quot;ocrImage&quot;]));
-echo &quot;Output image saved at &quot; . $ocr_image_path . &quot;\n&quot;;
-
-file_put_contents($layout_image_path, base64_decode($result[&quot;layoutImage&quot;]));
-echo &quot;Output image saved at &quot; . $layout_image_path . &quot;\n&quot;;
-
-echo &quot;\nDetected texts:\n&quot;;
-print_r($result[&quot;texts&quot;]);
-
-?&gt;
+for i, res in enumerate(result[&quot;sealRecResults&quot;]):
+    print(&quot;Detected texts:&quot;)
+    print(res[&quot;texts&quot;])
+    ocr_img_path = f&quot;ocr_{i}.jpg&quot;
+    with open(ocr_img_path, &quot;wb&quot;) as f:
+        f.write(base64.b64decode(res[&quot;ocrImage&quot;]))
+    layout_img_path = f&quot;layout_{i}.jpg&quot;
+    with open(layout_img_path, &quot;wb&quot;) as f:
+        f.write(base64.b64decode(res[&quot;layoutImage&quot;]))
+    print(f&quot;Output images saved at {ocr_img_path} and {layout_img_path}&quot;)
 </code></pre></details>
 </details>
 <br/>

+ 66 - 408
docs/pipeline_usage/tutorials/ocr_pipelines/seal_recognition.md

@@ -494,6 +494,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -503,9 +508,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -519,6 +528,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>
@@ -550,12 +564,18 @@ for res in output:
 </thead>
 <tbody>
 <tr>
-<td><code>image</code></td>
+<td><code>file</code></td>
 <td><code>string</code></td>
-<td>服务可访问的图像文件的URL或图像文件内容的Base64编码结果。</td>
+<td>服务可访问的图像文件或PDF文件的URL,或上述类型文件内容的Base64编码结果。对于超过10页的PDF文件,只有前10页的内容会被使用。</td>
 <td>是</td>
 </tr>
 <tr>
+<td><code>fileType</code></td>
+<td><code>integer</code></td>
+<td>文件类型。<code>0</code>表示PDF文件,<code>1</code>表示图像文件。若请求体无此属性,则服务将尝试根据URL自动推断文件类型。</td>
+<td>否</td>
+</tr>
+<tr>
 <td><code>inferenceParams</code></td>
 <td><code>object</code></td>
 <td>推理参数。</td>
@@ -595,11 +615,38 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>sealRecResults</code></td>
+<td><code>object</code></td>
+<td>印章文本识别结果。数组长度为1(对于图像输入)或文档页数与10中的较小者(对于PDF输入)。对于PDF输入,数组中的每个元素依次表示PDF文件中每一页的处理结果。</td>
+</tr>
+<tr>
+<td><code>dataInfo</code></td>
+<td><code>object</code></td>
+<td>输入数据信息。</td>
+</tr>
+</tbody>
+</table>
+<p><code>sealRecResults</code>中的每个元素为一个<code>object</code>,具有如下属性:</p>
+<table>
+<thead>
+<tr>
+<th>名称</th>
+<th>类型</th>
+<th>含义</th>
+</tr>
+</thead>
+<tbody>
+<tr>
 <td><code>texts</code></td>
 <td><code>array</code></td>
 <td>文本位置、内容和得分。</td>
 </tr>
 <tr>
+<td><code>inputImage</code></td>
+<td><code>string</code></td>
+<td>输入图像。图像为JPEG格式,使用Base64编码。</td>
+</tr>
+<tr>
 <td><code>layoutImage</code></td>
 <td><code>string</code></td>
 <td>版面区域检测结果图。图像为JPEG格式,使用Base64编码。</td>
@@ -648,418 +695,29 @@ for res in output:
 <pre><code class="language-python">import base64
 import requests
 
-API_URL = &quot;http://localhost:8080/seal-recognition&quot; # 服务URL
-image_path = &quot;./demo.jpg&quot;
-ocr_image_path = &quot;./ocr.jpg&quot;
-layout_image_path = &quot;./layout.jpg&quot;
+API_URL = &quot;http://localhost:8080/seal-recognition&quot;
+file_path = &quot;./demo.jpg&quot;
 
-# 对本地图像进行Base64编码
-with open(image_path, &quot;rb&quot;) as file:
-    image_bytes = file.read()
-    image_data = base64.b64encode(image_bytes).decode(&quot;ascii&quot;)
+with open(file_path, &quot;rb&quot;) as file:
+    file_bytes = file.read()
+    file_data = base64.b64encode(file_bytes).decode(&quot;ascii&quot;)
 
-payload = {&quot;image&quot;: image_data}  # Base64编码的文件内容或者图像URL
+payload = {&quot;file&quot;: file_data, &quot;fileType&quot;: 1}
 
-# 调用API
 response = requests.post(API_URL, json=payload)
 
-# 处理接口返回数据
 assert response.status_code == 200
 result = response.json()[&quot;result&quot;]
-with open(ocr_image_path, &quot;wb&quot;) as file:
-    file.write(base64.b64decode(result[&quot;ocrImage&quot;]))
-print(f&quot;Output image saved at {ocr_image_path}&quot;)
-with open(layout_image_path, &quot;wb&quot;) as file:
-    file.write(base64.b64decode(result[&quot;layoutImage&quot;]))
-print(f&quot;Output image saved at {layout_image_path}&quot;)
-print(&quot;\nDetected texts:&quot;)
-print(result[&quot;texts&quot;])
-</code></pre></details>
-
-<details><summary>C++</summary>
-
-<pre><code class="language-cpp">#include &lt;iostream&gt;
-#include &quot;cpp-httplib/httplib.h&quot; // https://github.com/Huiyicc/cpp-httplib
-#include &quot;nlohmann/json.hpp&quot; // https://github.com/nlohmann/json
-#include &quot;base64.hpp&quot; // https://github.com/tobiaslocker/base64
-
-int main() {
-    httplib::Client client(&quot;localhost:8080&quot;);
-    const std::string imagePath = &quot;./demo.jpg&quot;;
-    const std::string ocrImagePath = &quot;./ocr.jpg&quot;;
-    const std::string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    httplib::Headers headers = {
-        {&quot;Content-Type&quot;, &quot;application/json&quot;}
-    };
-
-    // 对本地图像进行Base64编码
-    std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
-    std::streamsize size = file.tellg();
-    file.seekg(0, std::ios::beg);
-
-    std::vector&lt;char&gt; buffer(size);
-    if (!file.read(buffer.data(), size)) {
-        std::cerr &lt;&lt; &quot;Error reading file.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-    std::string bufferStr(reinterpret_cast&lt;const char*&gt;(buffer.data()), buffer.size());
-    std::string encodedImage = base64::to_base64(bufferStr);
-
-    nlohmann::json jsonObj;
-    jsonObj[&quot;image&quot;] = encodedImage;
-    std::string body = jsonObj.dump();
-
-    // 调用API
-    auto response = client.Post(&quot;/seal-recognition&quot;, headers, body, &quot;application/json&quot;);
-    // 处理接口返回数据
-    if (response &amp;&amp; response-&gt;status == 200) {
-        nlohmann::json jsonResponse = nlohmann::json::parse(response-&gt;body);
-        auto result = jsonResponse[&quot;result&quot;];
-
-        encodedImage = result[&quot;ocrImage&quot;];
-        std::string decoded_string = base64::from_base64(encodedImage);
-        std::vector&lt;unsigned char&gt; decodedOcrImage(decoded_string.begin(), decoded_string.end());
-        std::ofstream outputOcrFile(ocrImagePath, std::ios::binary | std::ios::out);
-        if (outputOcrFile.is_open()) {
-            outputOcrFile.write(reinterpret_cast&lt;char*&gt;(decodedOcrImage.data()), decodedOcrImage.size());
-            outputOcrFile.close();
-            std::cout &lt;&lt; &quot;Output image saved at &quot; &lt;&lt; ocrImagePath &lt;&lt; std::endl;
-        } else {
-            std::cerr &lt;&lt; &quot;Unable to open file for writing: &quot; &lt;&lt; ocrImagePath &lt;&lt; std::endl;
-        }
-
-        encodedImage = result[&quot;layoutImage&quot;];
-        decodedString = base64::from_base64(encodedImage);
-        std::vector&lt;unsigned char&gt; decodedLayoutImage(decodedString.begin(), decodedString.end());
-        std::ofstream outputLayoutFile(layoutImagePath, std::ios::binary | std::ios::out);
-        if (outputLayoutFile.is_open()) {
-            outputLayoutFile.write(reinterpret_cast&lt;char*&gt;(decodedLayoutImage.data()), decodedLayoutImage.size());
-            outputLayoutFile.close();
-            std::cout &lt;&lt; &quot;Output image saved at &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        } else {
-            std::cerr &lt;&lt; &quot;Unable to open file for writing: &quot; &lt;&lt; layoutImagePath &lt;&lt; std::endl;
-        }
-
-        auto texts = result[&quot;texts&quot;];
-        std::cout &lt;&lt; &quot;\nDetected texts:&quot; &lt;&lt; std::endl;
-        for (const auto&amp; text : texts) {
-            std::cout &lt;&lt; text &lt;&lt; std::endl;
-        }
-    } else {
-        std::cout &lt;&lt; &quot;Failed to send HTTP request.&quot; &lt;&lt; std::endl;
-        return 1;
-    }
-
-    return 0;
-}
-</code></pre></details>
-
-<details><summary>Java</summary>
-
-<pre><code class="language-java">import okhttp3.*;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Base64;
-
-public class Main {
-    public static void main(String[] args) throws IOException {
-        String API_URL = &quot;http://localhost:8080/seal-recognition&quot;; // 服务URL
-        String imagePath = &quot;./demo.jpg&quot;; // 本地图像
-        String ocrImagePath = &quot;./ocr.jpg&quot;;
-        String layoutImagePath = &quot;./layout.jpg&quot;;
-
-        // 对本地图像进行Base64编码
-        File file = new File(imagePath);
-        byte[] fileContent = java.nio.file.Files.readAllBytes(file.toPath());
-        String imageData = Base64.getEncoder().encodeToString(fileContent);
-
-        ObjectMapper objectMapper = new ObjectMapper();
-        ObjectNode params = objectMapper.createObjectNode();
-        params.put(&quot;image&quot;, imageData); // Base64编码的文件内容或者图像URL
-
-        // 创建 OkHttpClient 实例
-        OkHttpClient client = new OkHttpClient();
-        MediaType JSON = MediaType.Companion.get(&quot;application/json; charset=utf-8&quot;);
-        RequestBody body = RequestBody.Companion.create(params.toString(), JSON);
-        Request request = new Request.Builder()
-                .url(API_URL)
-                .post(body)
-                .build();
-
-        // 调用API并处理接口返回数据
-        try (Response response = client.newCall(request).execute()) {
-            if (response.isSuccessful()) {
-                String responseBody = response.body().string();
-                JsonNode resultNode = objectMapper.readTree(responseBody);
-                JsonNode result = resultNode.get(&quot;result&quot;);
-                String ocrBase64Image = result.get(&quot;ocrImage&quot;).asText();
-                String layoutBase64Image = result.get(&quot;layoutImage&quot;).asText();
-                JsonNode texts = result.get(&quot;texts&quot;);
-
-                byte[] imageBytes = Base64.getDecoder().decode(ocrBase64Image);
-                try (FileOutputStream fos = new FileOutputStream(ocrImagePath)) {
-                    fos.write(imageBytes);
-                }
-                System.out.println(&quot;Output image saved at &quot; + ocrBase64Image);
-
-                imageBytes = Base64.getDecoder().decode(layoutBase64Image);
-                try (FileOutputStream fos = new FileOutputStream(layoutImagePath)) {
-                    fos.write(imageBytes);
-                }
-                System.out.println(&quot;Output image saved at &quot; + layoutImagePath);
-
-                System.out.println(&quot;\nDetected texts: &quot; + texts.toString());
-            } else {
-                System.err.println(&quot;Request failed with code: &quot; + response.code());
-            }
-        }
-    }
-}
-</code></pre></details>
-
-<details><summary>Go</summary>
-
-<pre><code class="language-go">package main
-
-import (
-    &quot;bytes&quot;
-    &quot;encoding/base64&quot;
-    &quot;encoding/json&quot;
-    &quot;fmt&quot;
-    &quot;io/ioutil&quot;
-    &quot;net/http&quot;
-)
-
-func main() {
-    API_URL := &quot;http://localhost:8080/seal-recognition&quot;
-    imagePath := &quot;./demo.jpg&quot;
-    ocrImagePath := &quot;./ocr.jpg&quot;
-    layoutImagePath := &quot;./layout.jpg&quot;
-
-    // 对本地图像进行Base64编码
-    imageBytes, err := ioutil.ReadFile(imagePath)
-    if err != nil {
-        fmt.Println(&quot;Error reading image file:&quot;, err)
-        return
-    }
-    imageData := base64.StdEncoding.EncodeToString(imageBytes)
-
-    payload := map[string]string{&quot;image&quot;: imageData} // Base64编码的文件内容或者图像URL
-    payloadBytes, err := json.Marshal(payload)
-    if err != nil {
-        fmt.Println(&quot;Error marshaling payload:&quot;, err)
-        return
-    }
-
-    // 调用API
-    client := &amp;http.Client{}
-    req, err := http.NewRequest(&quot;POST&quot;, API_URL, bytes.NewBuffer(payloadBytes))
-    if err != nil {
-        fmt.Println(&quot;Error creating request:&quot;, err)
-        return
-    }
-
-    res, err := client.Do(req)
-    if err != nil {
-        fmt.Println(&quot;Error sending request:&quot;, err)
-        return
-    }
-    defer res.Body.Close()
-
-    // 处理接口返回数据
-    body, err := ioutil.ReadAll(res.Body)
-    if err != nil {
-        fmt.Println(&quot;Error reading response body:&quot;, err)
-        return
-    }
-    type Response struct {
-        Result struct {
-            OcrImage      string   `json:&quot;ocrImage&quot;`
-            LayoutImage      string   `json:&quot;layoutImage&quot;`
-            Texts []map[string]interface{} `json:&quot;texts&quot;`
-        } `json:&quot;result&quot;`
-    }
-    var respData Response
-    err = json.Unmarshal([]byte(string(body)), &amp;respData)
-    if err != nil {
-        fmt.Println(&quot;Error unmarshaling response body:&quot;, err)
-        return
-    }
-
-    ocrImageData, err := base64.StdEncoding.DecodeString(respData.Result.OcrImage)
-    if err != nil {
-        fmt.Println(&quot;Error decoding base64 image data:&quot;, err)
-        return
-    }
-    err = ioutil.WriteFile(ocrImagePath, ocrImageData, 0644)
-    if err != nil {
-        fmt.Println(&quot;Error writing image to file:&quot;, err)
-        return
-    }
-    fmt.Printf(&quot;Image saved at %s.jpg\n&quot;, ocrImagePath)
-
-    layoutImageData, err := base64.StdEncoding.DecodeString(respData.Result.LayoutImage)
-    if err != nil {
-        fmt.Println(&quot;Error decoding base64 image data:&quot;, err)
-        return
-    }
-    err = ioutil.WriteFile(layoutImagePath, layoutImageData, 0644)
-    if err != nil {
-        fmt.Println(&quot;Error writing image to file:&quot;, err)
-        return
-    }
-    fmt.Printf(&quot;Image saved at %s.jpg\n&quot;, layoutImagePath)
-
-    fmt.Println(&quot;\nDetected texts:&quot;)
-    for _, text := range respData.Result.Texts {
-        fmt.Println(text)
-    }
-}
-</code></pre></details>
-
-<details><summary>C#</summary>
-
-<pre><code class="language-csharp">using System;
-using System.IO;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
-
-class Program
-{
-    static readonly string API_URL = &quot;http://localhost:8080/seal-recognition&quot;;
-    static readonly string imagePath = &quot;./demo.jpg&quot;;
-    static readonly string ocrImagePath = &quot;./ocr.jpg&quot;;
-    static readonly string layoutImagePath = &quot;./layout.jpg&quot;;
-
-    static async Task Main(string[] args)
-    {
-        var httpClient = new HttpClient();
-
-        // 对本地图像进行Base64编码
-        byte[] imageBytes = File.ReadAllBytes(imagePath);
-        string image_data = Convert.ToBase64String(imageBytes);
-
-        var payload = new JObject{ { &quot;image&quot;, image_data } }; // Base64编码的文件内容或者图像URL
-        var content = new StringContent(payload.ToString(), Encoding.UTF8, &quot;application/json&quot;);
-
-        // 调用API
-        HttpResponseMessage response = await httpClient.PostAsync(API_URL, content);
-        response.EnsureSuccessStatusCode();
-
-        // 处理接口返回数据
-        string responseBody = await response.Content.ReadAsStringAsync();
-        JObject jsonResponse = JObject.Parse(responseBody);
-
-        string ocrBase64Image = jsonResponse[&quot;result&quot;][&quot;ocrImage&quot;].ToString();
-        byte[] ocrImageBytes = Convert.FromBase64String(ocrBase64Image);
-        File.WriteAllBytes(ocrImagePath, ocrImageBytes);
-        Console.WriteLine($&quot;Output image saved at {ocrImagePath}&quot;);
-
-        string layoutBase64Image = jsonResponse[&quot;result&quot;][&quot;layoutImage&quot;].ToString();
-        byte[] layoutImageBytes = Convert.FromBase64String(layoutBase64Image);
-        File.WriteAllBytes(layoutImagePath, layoutImageBytes);
-        Console.WriteLine($&quot;Output image saved at {layoutImagePath}&quot;);
-
-        Console.WriteLine(&quot;\nDetected texts:&quot;);
-        Console.WriteLine(jsonResponse[&quot;result&quot;][&quot;texts&quot;].ToString());
-    }
-}
-</code></pre></details>
-
-<details><summary>Node.js</summary>
-
-<pre><code class="language-js">const axios = require('axios');
-const fs = require('fs');
-
-const API_URL = 'http://localhost:8080/seal-recognition'
-const imagePath = './demo.jpg'
-const ocrImagePath = &quot;./ocr.jpg&quot;;
-const layoutImagePath = &quot;./layout.jpg&quot;;
-
-let config = {
-   method: 'POST',
-   maxBodyLength: Infinity,
-   url: API_URL,
-   data: JSON.stringify({
-    'image': encodeImageToBase64(imagePath)  // Base64编码的文件内容或者图像URL
-  })
-};
-
-// 对本地图像进行Base64编码
-function encodeImageToBase64(filePath) {
-  const bitmap = fs.readFileSync(filePath);
-  return Buffer.from(bitmap).toString('base64');
-}
-
-// 调用API
-axios.request(config)
-.then((response) =&gt; {
-    // 处理接口返回数据
-    const result = response.data[&quot;result&quot;];
-
-    const imageBuffer = Buffer.from(result[&quot;ocrImage&quot;], 'base64');
-    fs.writeFile(ocrImagePath, imageBuffer, (err) =&gt; {
-      if (err) throw err;
-      console.log(`Output image saved at ${ocrImagePath}`);
-    });
-
-    imageBuffer = Buffer.from(result[&quot;layoutImage&quot;], 'base64');
-    fs.writeFile(layoutImagePath, imageBuffer, (err) =&gt; {
-      if (err) throw err;
-      console.log(`Output image saved at ${layoutImagePath}`);
-    });
-
-    console.log(&quot;\nDetected texts:&quot;);
-    console.log(result[&quot;texts&quot;]);
-})
-.catch((error) =&gt; {
-  console.log(error);
-});
-</code></pre></details>
-
-<details><summary>PHP</summary>
-
-<pre><code class="language-php">&lt;?php
-
-$API_URL = &quot;http://localhost:8080/seal-recognition&quot;; // 服务URL
-$image_path = &quot;./demo.jpg&quot;;
-$ocr_image_path = &quot;./ocr.jpg&quot;;
-$layout_image_path = &quot;./layout.jpg&quot;;
-
-// 对本地图像进行Base64编码
-$image_data = base64_encode(file_get_contents($image_path));
-$payload = array(&quot;image&quot; =&gt; $image_data); // Base64编码的文件内容或者图像URL
-
-// 调用API
-$ch = curl_init($API_URL);
-curl_setopt($ch, CURLOPT_POST, true);
-curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
-curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-$response = curl_exec($ch);
-curl_close($ch);
-
-// 处理接口返回数据
-$result = json_decode($response, true)[&quot;result&quot;];
-file_put_contents($ocr_image_path, base64_decode($result[&quot;ocrImage&quot;]));
-echo &quot;Output image saved at &quot; . $ocr_image_path . &quot;\n&quot;;
-
-file_put_contents($layout_image_path, base64_decode($result[&quot;layoutImage&quot;]));
-echo &quot;Output image saved at &quot; . $layout_image_path . &quot;\n&quot;;
-
-echo &quot;\nDetected texts:\n&quot;;
-print_r($result[&quot;texts&quot;]);
-
-?&gt;
+for i, res in enumerate(result[&quot;sealRecResults&quot;]):
+    print(&quot;Detected texts:&quot;)
+    print(res[&quot;texts&quot;])
+    ocr_img_path = f&quot;ocr_{i}.jpg&quot;
+    with open(ocr_img_path, &quot;wb&quot;) as f:
+        f.write(base64.b64decode(res[&quot;ocrImage&quot;]))
+    layout_img_path = f&quot;layout_{i}.jpg&quot;
+    with open(layout_img_path, &quot;wb&quot;) as f:
+        f.write(base64.b64decode(res[&quot;layoutImage&quot;]))
+    print(f&quot;Output images saved at {ocr_img_path} and {layout_img_path}&quot;)
 </code></pre></details>
 </details>
 <br/>

+ 15 - 1
docs/pipeline_usage/tutorials/ocr_pipelines/table_recognition.en.md

@@ -371,6 +371,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -380,9 +385,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -396,6 +405,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/ocr_pipelines/table_recognition.md

@@ -455,6 +455,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -464,9 +469,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -480,6 +489,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/time_series_pipelines/time_series_anomaly_detection.en.md

@@ -278,6 +278,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -287,9 +292,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error description. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the properties of the response body are as follows:</li>
 </ul>
@@ -303,6 +312,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/time_series_pipelines/time_series_anomaly_detection.md

@@ -281,6 +281,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -290,9 +295,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -306,6 +315,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/time_series_pipelines/time_series_classification.en.md

@@ -243,6 +243,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -252,9 +257,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -268,6 +277,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/time_series_pipelines/time_series_classification.md

@@ -240,6 +240,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -249,9 +254,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -265,6 +274,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 15 - 1
docs/pipeline_usage/tutorials/time_series_pipelines/time_series_forecasting.en.md

@@ -287,6 +287,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Fixed as <code>0</code>.</td>
@@ -296,9 +301,13 @@ Below are the API references and multi-language service invocation examples:
 <td><code>string</code></td>
 <td>Error message. Fixed as <code>"Success"</code>.</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>Operation result.</td>
+</tr>
 </tbody>
 </table>
-<p>The response body may also have a <code>result</code> property of type <code>object</code>, which stores the operation result information.</p>
 <ul>
 <li>When the request is not processed successfully, the response body properties are as follows:</li>
 </ul>
@@ -312,6 +321,11 @@ Below are the API references and multi-language service invocation examples:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>UUID for the request.</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>Error code. Same as the response status code.</td>

+ 15 - 1
docs/pipeline_usage/tutorials/time_series_pipelines/time_series_forecasting.md

@@ -289,6 +289,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。固定为<code>0</code>。</td>
@@ -298,9 +303,13 @@ for res in output:
 <td><code>string</code></td>
 <td>错误说明。固定为<code>"Success"</code>。</td>
 </tr>
+<tr>
+<td><code>result</code></td>
+<td><code>object</code></td>
+<td>操作结果。</td>
+</tr>
 </tbody>
 </table>
-<p>响应体还可能有<code>result</code>属性,类型为<code>object</code>,其中存储操作结果信息。</p>
 <ul>
 <li>当请求处理未成功时,响应体的属性如下:</li>
 </ul>
@@ -314,6 +323,11 @@ for res in output:
 </thead>
 <tbody>
 <tr>
+<td><code>logId</code></td>
+<td><code>string</code></td>
+<td>请求的UUID。</td>
+</tr>
+<tr>
 <td><code>errorCode</code></td>
 <td><code>integer</code></td>
 <td>错误码。与响应状态码相同。</td>

+ 3 - 2
paddlex/inference/pipelines/__init__.py

@@ -13,7 +13,7 @@
 # limitations under the License.
 
 from pathlib import Path
-from typing import Any, Dict, Optional
+from typing import Any, Dict, Mapping, Optional
 
 from ...utils.config import parse_config
 from ..utils.get_pipeline_path import get_pipeline_path
@@ -59,7 +59,7 @@ def load_pipeline_config(pipeline: str) -> Dict[str, Any]:
 
 
 def create_pipeline_from_config(
-    config: Dict[str, Any],
+    config: Mapping[str, Any],
     device=None,
     pp_option=None,
     use_hpip: bool = False,
@@ -73,6 +73,7 @@ def create_pipeline_from_config(
     predictor_kwargs = {"use_hpip": use_hpip}
     if "use_hpip" in pipeline_setting:
         predictor_kwargs["use_hpip"] = use_hpip
+        pipeline_setting.pop("use_hpip", None)
     if hpi_params is not None:
         predictor_kwargs["hpi_params"] = hpi_params
         pipeline_setting.pop("hpi_params", None)

+ 4 - 2
paddlex/inference/pipelines/pp_shitu_v2.py

@@ -40,10 +40,12 @@ class ShiTuV2Pipeline(BasePipeline):
         return_k=5,
         device=None,
         predictor_kwargs=None,
+        _build_models=True,
     ):
         super().__init__(device, predictor_kwargs)
-        self._build_predictor(det_model, rec_model)
-        self.set_predictor(det_batch_size, rec_batch_size, device)
+        if _build_models:
+            self._build_predictor(det_model, rec_model)
+            self.set_predictor(det_batch_size, rec_batch_size, device)
         self._return_k, self._score_thres, self._hamming_radius = (
             return_k,
             score_thres,

+ 5 - 0
paddlex/inference/pipelines/ppchatocrv3/ppchatocrv3.py

@@ -83,6 +83,11 @@ class PPChatOCRPipeline(_TableRecPipeline):
                 doc_image_unwarp_batch_size=doc_image_unwarp_batch_size,
                 seal_text_det_batch_size=seal_text_det_batch_size,
             )
+        else:
+            self.llm_api = create_llm_api(
+                llm_name,
+                llm_params,
+            )
 
         # get base prompt from yaml info
         if task_prompt_yaml:

+ 13 - 0
paddlex/inference/pipelines/serving/_pipeline_apps/_common/__init__.py

@@ -0,0 +1,13 @@
+# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.

+ 49 - 0
paddlex/inference/pipelines/serving/_pipeline_apps/_common/cv.py

@@ -0,0 +1,49 @@
+# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+from typing import Optional, Tuple
+
+import cv2
+import numpy as np
+from numpy.typing import ArrayLike
+
+from ... import utils as serving_utils
+from ...storage import Storage, SupportsGetURL
+
+
+def postprocess_image(
+    image: ArrayLike,
+    log_id: str,
+    filename: str,
+    file_storage: Optional[Storage],
+    *,
+    max_img_size: Optional[Tuple[int, int]] = None,
+) -> str:
+    key = f"{log_id}/{filename}"
+    ext = os.path.splitext(filename)[1]
+    image = np.asarray(image)
+    h, w = image.shape[0:2]
+    if w > max_img_size[1] or h > max_img_size[0]:
+        if w / h > max_img_size[0] / max_img_size[1]:
+            factor = max_img_size[0] / w
+        else:
+            factor = max_img_size[1] / h
+        image = cv2.resize(image, (int(factor * w), int(factor * h)))
+    img_bytes = serving_utils.image_array_to_bytes(image, ext=ext)
+    if file_storage is not None:
+        file_storage.set(key, img_bytes)
+        if isinstance(file_storage, SupportsGetURL):
+            return file_storage.get_url(key)
+    return serving_utils.base64_encode(img_bytes)

+ 152 - 0
paddlex/inference/pipelines/serving/_pipeline_apps/_common/ocr.py

@@ -0,0 +1,152 @@
+# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import asyncio
+from typing import Awaitable, Final, List, Literal, Optional, Tuple
+
+import numpy as np
+from fastapi import HTTPException
+from numpy.typing import ArrayLike
+from pydantic import BaseModel, Field
+from typing_extensions import Annotated, TypeAlias, assert_never
+
+from ......utils import logging
+from ... import utils as serving_utils
+from .cv import postprocess_image
+from ...models import DataInfo, ImageInfo, PDFInfo
+from ...storage import create_storage
+from ...app import AppContext
+
+DEFAULT_MAX_NUM_INPUT_IMGS: Final[int] = 10
+DEFAULT_MAX_OUTPUT_IMG_SIZE: Final[Tuple[int, int]] = (2000, 2000)
+
+FileType: TypeAlias = Literal[0, 1]
+
+
+class InferenceParams(BaseModel):
+    maxLongSide: Optional[Annotated[int, Field(gt=0)]] = None
+
+
+class InferRequest(BaseModel):
+    file: str
+    fileType: Optional[FileType] = None
+    inferenceParams: Optional[InferenceParams] = None
+
+
+def update_app_context(app_context: AppContext) -> None:
+    extra_cfg = app_context.config.extra or {}
+    app_context.extra["file_storage"] = None
+    if "file_storage" in extra_cfg:
+        app_context.extra["file_storage"] = create_storage(extra_cfg["file_storage"])
+    app_context.extra["max_num_input_imgs"] = extra_cfg.get(
+        "max_num_input_imgs", DEFAULT_MAX_NUM_INPUT_IMGS
+    )
+    app_context.extra["max_output_img_size"] = extra_cfg.get(
+        "max_output_img_size", DEFAULT_MAX_OUTPUT_IMG_SIZE
+    )
+
+
+def get_file_type(request: InferRequest) -> Literal["IMAGE", "PDF"]:
+    if request.fileType is None:
+        if serving_utils.is_url(request.file):
+            try:
+                file_type = serving_utils.infer_file_type(request.file)
+            except Exception:
+                logging.exception("Failed to infer the file type")
+                raise HTTPException(
+                    status_code=422,
+                    detail="The file type cannot be inferred from the URL. Please specify the file type explicitly.",
+                )
+        else:
+            raise HTTPException(status_code=422, detail="Unknown file type")
+    else:
+        file_type = "PDF" if request.fileType == 0 else "IMAGE"
+
+    return file_type
+
+
+async def get_images(
+    request: InferRequest, app_context: AppContext
+) -> Tuple[List[np.ndarray], DataInfo]:
+    file_type = get_file_type(request)
+    # XXX: Currently, we use 500 for consistency. However, 422 may be more
+    # appropriate.
+    try:
+        file_bytes = await serving_utils.get_raw_bytes(
+            request.file,
+            app_context.aiohttp_session,
+        )
+        images, data_info = await serving_utils.call_async(
+            serving_utils.file_to_images,
+            file_bytes,
+            file_type,
+            max_num_imgs=app_context.extra["max_num_input_imgs"],
+        )
+    except Exception:
+        logging.exception("Unexpected exception")
+        raise HTTPException(status_code=500, detail="Internal server error")
+
+    if file_type == "IMAGE":
+        return images, DataInfo(image=data_info)
+    elif file_type == "PDF":
+        return images, DataInfo(pdf=data_info)
+    else:
+        assert_never()
+
+
+async def postprocess_images(
+    *,
+    log_id: str,
+    index: str,
+    app_context: AppContext,
+    input_image: Optional[ArrayLike] = None,
+    ocr_image: Optional[ArrayLike] = None,
+    layout_image: Optional[ArrayLike] = None,
+) -> List[str]:
+    if input_image is None and ocr_image is None and layout_image is None:
+        raise ValueError("At least one of the images must be provided.")
+    file_storage = app_context.extra["file_storage"]
+    max_img_size = app_context.extra["max_output_img_size"]
+    futures: List[Awaitable] = []
+    if input_image is not None:
+        future = serving_utils.call_async(
+            postprocess_image,
+            input_image,
+            log_id=log_id,
+            filename=f"input_image_{index}.jpg",
+            file_storage=file_storage,
+            max_img_size=max_img_size,
+        )
+        futures.append(future)
+    if ocr_image is not None:
+        future = serving_utils.call_async(
+            postprocess_image,
+            ocr_image,
+            log_id=log_id,
+            filename=f"ocr_image_{index}.jpg",
+            file_storage=file_storage,
+            max_img_size=max_img_size,
+        )
+        futures.append(future)
+    if layout_image is not None:
+        future = serving_utils.call_async(
+            postprocess_image,
+            layout_image,
+            log_id=log_id,
+            filename=f"layout_image_{index}.jpg",
+            file_storage=file_storage,
+            max_img_size=max_img_size,
+        )
+        futures.append(future)
+    return await asyncio.gather(*futures)

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/anomaly_detection.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...single_model_pipeline import AnomalyDetection
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -43,7 +43,8 @@ def create_pipeline_app(pipeline: AnomalyDetection, app_config: AppConfig) -> Fa
     @app.post(
         "/image-anomaly-detection",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -64,17 +65,15 @@ def create_pipeline_app(pipeline: AnomalyDetection, app_config: AppConfig) -> Fa
                 serving_utils.image_to_bytes(result.img.convert("RGB"))
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(
                     labelMap=label_map, size=size, image=output_image_base64
                 ),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 23 - 28
paddlex/inference/pipelines/serving/_pipeline_apps/face_recognition.py

@@ -15,6 +15,7 @@
 import asyncio
 import faiss
 import pickle
+import uuid
 from typing import Dict, List, Optional
 
 from fastapi import FastAPI, HTTPException
@@ -27,7 +28,7 @@ from ...face_recognition import FaceRecPipeline
 from ..storage import create_storage
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class ImageLabelPair(BaseModel):
@@ -110,7 +111,8 @@ def create_pipeline_app(pipeline: FaceRecPipeline, app_config: AppConfig) -> Fas
     @app.post(
         "/face-recognition-index-build",
         operation_id="buildIndex",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _build_index(
         request: BuildIndexRequest,
@@ -118,8 +120,6 @@ def create_pipeline_app(pipeline: FaceRecPipeline, app_config: AppConfig) -> Fas
         pipeline = ctx.pipeline
         aiohttp_session = ctx.aiohttp_session
 
-        request_id = serving_utils.generate_request_id()
-
         try:
             images = [pair.image for pair in request.imageLabelPairs]
             file_bytes_list = await asyncio.gather(
@@ -141,7 +141,7 @@ def create_pipeline_app(pipeline: FaceRecPipeline, app_config: AppConfig) -> Fas
             )
 
             index_storage = ctx.extra["index_storage"]
-            index_key = request_id
+            index_key = str(uuid.uuid4())
             index_data_bytes = await serving_utils.call_async(
                 _serialize_index_data, index_data
             )
@@ -149,21 +149,20 @@ def create_pipeline_app(pipeline: FaceRecPipeline, app_config: AppConfig) -> Fas
                 index_storage.set, index_key, index_data_bytes
             )
 
-            return ResultResponse(
+            return ResultResponse[BuildIndexResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=BuildIndexResult(indexKey=index_key, idMap=index_data.id_map),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/face-recognition-index-add",
         operation_id="buildIndex",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _add_images_to_index(
         request: AddImagesToIndexRequest,
@@ -199,21 +198,20 @@ def create_pipeline_app(pipeline: FaceRecPipeline, app_config: AppConfig) -> Fas
                 index_storage.set, request.indexKey, index_data_bytes
             )
 
-            return ResultResponse(
+            return ResultResponse[AddImagesToIndexResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=AddImagesToIndexResult(idMap=index_data.id_map),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/face-recognition-index-remove",
         operation_id="buildIndex",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _remove_images_from_index(
         request: RemoveImagesFromIndexRequest,
@@ -240,21 +238,20 @@ def create_pipeline_app(pipeline: FaceRecPipeline, app_config: AppConfig) -> Fas
                 index_storage.set, request.indexKey, index_data_bytes
             )
 
-            return ResultResponse(
+            return ResultResponse[RemoveImagesFromIndexResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=RemoveImagesFromIndexResult(idMap=index_data.id_map),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/face-recognition-infer",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -303,15 +300,13 @@ def create_pipeline_app(pipeline: FaceRecPipeline, app_config: AppConfig) -> Fas
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(faces=faces, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 64 - 44
paddlex/inference/pipelines/serving/_pipeline_apps/formula_recognition.py

@@ -12,26 +12,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from typing import List, Optional
+from typing import List, Optional, Type
 
 from fastapi import FastAPI, HTTPException
 from pydantic import BaseModel, Field
 from typing_extensions import Annotated, TypeAlias
 
+from ._common import ocr as ocr_common
 from .....utils import logging
 from ...formula_recognition import FormulaRecognitionPipeline
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse, DataInfo
 
-
-class InferenceParams(BaseModel):
-    maxLongSide: Optional[Annotated[int, Field(gt=0)]] = None
-
-
-class InferRequest(BaseModel):
-    image: str
-    inferenceParams: Optional[InferenceParams] = None
+InferRequest: Type[ocr_common.InferRequest] = ocr_common.InferRequest
 
 
 Point: TypeAlias = Annotated[List[float], Field(min_length=2, max_length=2)]
@@ -43,12 +37,18 @@ class Formula(BaseModel):
     latex: str
 
 
-class InferResult(BaseModel):
+class FormulaRecResult(BaseModel):
     formulas: List[Formula]
+    inputImage: str
     layoutImage: str
     ocrImage: Optional[str] = None
 
 
+class InferResult(BaseModel):
+    formulaRecResults: List[FormulaRecResult]
+    dataInfo: DataInfo
+
+
 def create_pipeline_app(
     pipeline: FormulaRecognitionPipeline, app_config: AppConfig
 ) -> FastAPI:
@@ -56,15 +56,22 @@ def create_pipeline_app(
         pipeline=pipeline, app_config=app_config, app_aiohttp_session=True
     )
 
+    ocr_common.update_app_context(ctx)
+    ctx.extra["return_ocr_imgs"] = False
+    if ctx.config.extra:
+        if "return_ocr_imgs" in ctx.config.extra:
+            ctx.extra["return_ocr_imgs"] = ctx.config.extra["return_ocr_imgs"]
+
     @app.post(
         "/formula-recognition",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
         response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
-        aiohttp_session = ctx.aiohttp_session
+
+        log_id = serving_utils.generate_log_id()
 
         if request.inferenceParams:
             max_long_side = request.inferenceParams.maxLongSide
@@ -74,46 +81,59 @@ def create_pipeline_app(
                     detail="`max_long_side` is currently not supported.",
                 )
 
-        try:
-            file_bytes = await serving_utils.get_raw_bytes(
-                request.image, aiohttp_session
-            )
-            image = serving_utils.image_bytes_to_array(file_bytes)
-
-            result = (await pipeline.infer(image))[0]
+        images, data_info = await ocr_common.get_images(request, ctx)
 
-            formulas: List[Formula] = []
-            for poly, latex in zip(result["dt_polys"], result["rec_formula"]):
-                formulas.append(
-                    Formula(
-                        poly=poly,
-                        latex=latex,
+        try:
+            result = await pipeline.infer(images)
+
+            formula_rec_results: List[FormulaRecResult] = []
+            for i, (img, item) in enumerate(zip(images, result)):
+                formulas: List[Formula] = []
+                for poly, latex in zip(item["dt_polys"], item["rec_formula"]):
+                    formulas.append(
+                        Formula(
+                            poly=poly,
+                            latex=latex,
+                        )
                     )
+                layout_img = item["layout_result"].img
+                if ctx.extra["return_ocr_imgs"]:
+                    ocr_img = item["formula_result"].img
+                    if ocr_img is None:
+                        raise RuntimeError("Failed to get the OCR image")
+                else:
+                    ocr_img = None
+                output_imgs = await ocr_common.postprocess_images(
+                    log_id=log_id,
+                    index=i,
+                    app_context=ctx,
+                    input_image=img,
+                    ocr_image=ocr_img,
+                    layout_image=layout_img,
                 )
-            layout_image_base64 = serving_utils.base64_encode(
-                serving_utils.image_to_bytes(result["layout_result"].img)
-            )
-            ocr_image = result["formula_result"].img
-            if ocr_image is not None:
-                ocr_image_base64 = serving_utils.base64_encode(
-                    serving_utils.image_to_bytes(ocr_image)
+                if ocr_img is not None:
+                    input_img, layout_img, ocr_img = output_imgs
+                else:
+                    input_img, layout_img = output_imgs
+                formula_rec_results.append(
+                    FormulaRecResult(
+                        formulas=formulas,
+                        inputImage=input_img,
+                        layoutImage=layout_img,
+                        ocrImage=ocr_img,
+                    )
                 )
-            else:
-                ocr_image_base64 = None
 
-            return ResultResponse(
-                logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
+            return ResultResponse[InferResult](
+                logId=log_id,
                 result=InferResult(
-                    formulas=formulas,
-                    layoutImage=layout_image_base64,
-                    ocrImage=ocr_image_base64,
+                    formulaRecResults=formula_rec_results,
+                    dataInfo=data_info,
                 ),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/image_classification.py

@@ -23,7 +23,7 @@ from .....utils import logging
 from ...single_model_pipeline import ImageClassification
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferenceParams(BaseModel):
@@ -56,7 +56,8 @@ def create_pipeline_app(
     @app.post(
         "/image-classification",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -87,15 +88,13 @@ def create_pipeline_app(
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(categories=categories, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/instance_segmentation.py

@@ -24,7 +24,7 @@ from .....utils import logging
 from ...single_model_pipeline import InstanceSegmentation
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -68,7 +68,8 @@ def create_pipeline_app(
     @app.post(
         "/instance-segmentation",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -98,15 +99,13 @@ def create_pipeline_app(
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(instances=instances, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 21 - 83
paddlex/inference/pipelines/serving/_pipeline_apps/layout_parsing.py

@@ -12,40 +12,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import os
-from typing import Final, List, Literal, Optional, Tuple
+from typing import List, Literal, Optional
 
-import numpy as np
 from fastapi import FastAPI, HTTPException
-from numpy.typing import ArrayLike
 from pydantic import BaseModel, Field
 from typing_extensions import Annotated, TypeAlias
 
+from ._common import cv as cv_common, ocr as ocr_common
 from .....utils import logging
 from ...layout_parsing import LayoutParsingPipeline
-from ..storage import SupportsGetURL, Storage, create_storage
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse, DataInfo
 
-_DEFAULT_MAX_IMG_SIZE: Final[Tuple[int, int]] = (2000, 2000)
-_DEFAULT_MAX_NUM_IMGS: Final[int] = 10
 
-
-FileType: TypeAlias = Literal[0, 1]
-
-
-class InferenceParams(BaseModel):
-    maxLongSide: Optional[Annotated[int, Field(gt=0)]] = None
-
-
-class InferRequest(BaseModel):
-    file: str
-    fileType: Optional[FileType] = None
+class InferRequest(ocr_common.InferRequest):
     useImgOrientationCls: bool = True
-    useImgUnwrapping: bool = True
+    useImgUnwarping: bool = True
     useSealTextDet: bool = True
-    inferenceParams: Optional[InferenceParams] = None
 
 
 BoundingBox: TypeAlias = Annotated[List[float], Field(min_length=4, max_length=4)]
@@ -65,23 +49,7 @@ class LayoutParsingResult(BaseModel):
 
 class InferResult(BaseModel):
     layoutParsingResults: List[LayoutParsingResult]
-
-
-def _postprocess_image(
-    img: ArrayLike,
-    request_id: str,
-    filename: str,
-    file_storage: Optional[Storage],
-) -> str:
-    key = f"{request_id}/{filename}"
-    ext = os.path.splitext(filename)[1]
-    img = np.asarray(img)
-    img_bytes = serving_utils.image_array_to_bytes(img, ext=ext)
-    if file_storage is not None:
-        file_storage.set(key, img_bytes)
-        if isinstance(file_storage, SupportsGetURL):
-            return file_storage.get_url(key)
-    return serving_utils.base64_encode(img_bytes)
+    dataInfo: DataInfo
 
 
 def create_pipeline_app(
@@ -91,41 +59,20 @@ def create_pipeline_app(
         pipeline=pipeline, app_config=app_config, app_aiohttp_session=True
     )
 
-    if ctx.config.extra and "file_storage" in ctx.config.extra:
-        ctx.extra["file_storage"] = create_storage(ctx.config.extra["file_storage"])
-    else:
-        ctx.extra["file_storage"] = None
-    ctx.extra.setdefault("max_img_size", _DEFAULT_MAX_IMG_SIZE)
-    ctx.extra.setdefault("max_num_imgs", _DEFAULT_MAX_NUM_IMGS)
+    ocr_common.update_app_context(ctx)
 
     @app.post(
         "/layout-parsing",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
         response_model_exclude_none=True,
     )
     async def _infer(
         request: InferRequest,
     ) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
-        aiohttp_session = ctx.aiohttp_session
-
-        request_id = serving_utils.generate_request_id()
-
-        if request.fileType is None:
-            if serving_utils.is_url(request.file):
-                try:
-                    file_type = serving_utils.infer_file_type(request.file)
-                except Exception as e:
-                    logging.exception(e)
-                    raise HTTPException(
-                        status_code=422,
-                        detail="The file type cannot be inferred from the URL. Please specify the file type explicitly.",
-                    )
-            else:
-                raise HTTPException(status_code=422, detail="Unknown file type")
-        else:
-            file_type = "PDF" if request.fileType == 0 else "IMAGE"
+
+        log_id = serving_utils.generate_log_id()
 
         if request.inferenceParams:
             max_long_side = request.inferenceParams.maxLongSide
@@ -135,22 +82,13 @@ def create_pipeline_app(
                     detail="`max_long_side` is currently not supported.",
                 )
 
-        try:
-            file_bytes = await serving_utils.get_raw_bytes(
-                request.file, aiohttp_session
-            )
-            images = await serving_utils.call_async(
-                serving_utils.file_to_images,
-                file_bytes,
-                file_type,
-                max_img_size=ctx.extra["max_img_size"],
-                max_num_imgs=ctx.extra["max_num_imgs"],
-            )
+        images, data_info = await ocr_common.get_images(request, ctx)
 
+        try:
             result = await pipeline.infer(
                 images,
                 use_doc_image_ori_cls_model=request.useImgOrientationCls,
-                use_doc_image_unwarp_model=request.useImgUnwrapping,
+                use_doc_image_unwarp_model=request.useImgUnwarping,
                 use_seal_text_det_model=request.useSealTextDet,
             )
 
@@ -166,11 +104,12 @@ def create_pipeline_app(
                     label = next(iter(dyn_keys))
                     if label in ("image", "figure", "img", "fig"):
                         image_ = await serving_utils.call_async(
-                            _postprocess_image,
+                            cv_common.postprocess_image,
                             subitem[label]["img"],
-                            request_id=request_id,
+                            log_id=log_id,
                             filename=f"image_{i}_{j}.jpg",
                             file_storage=ctx.extra["file_storage"],
+                            max_img_size=ctx.extra["max_output_img_size"],
                         )
                         text = subitem[label]["image_text"]
                     else:
@@ -189,17 +128,16 @@ def create_pipeline_app(
                     LayoutParsingResult(layoutElements=layout_elements)
                 )
 
-            return ResultResponse(
-                logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
+            return ResultResponse[InferResult](
+                logId=log_id,
                 result=InferResult(
                     layoutParsingResults=layout_parsing_results,
+                    dataInfo=data_info,
                 ),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 20 - 8
paddlex/inference/pipelines/serving/_pipeline_apps/multi_label_image_classification.py

@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from typing import List
+from typing import List, Optional
 
 from fastapi import FastAPI, HTTPException
 from pydantic import BaseModel
@@ -21,11 +21,16 @@ from .....utils import logging
 from ...single_model_pipeline import ImageClassification
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
+
+
+class InferenceParams(BaseModel):
+    threshold: Optional[float] = None
 
 
 class InferRequest(BaseModel):
     image: str
+    inferenceParams: Optional[InferenceParams] = None
 
 
 class Category(BaseModel):
@@ -49,12 +54,21 @@ def create_pipeline_app(
     @app.post(
         "/multilabel-image-classification",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
         aiohttp_session = ctx.aiohttp_session
 
+        if request.inferenceParams:
+            threshold = request.inferenceParams.threshold
+            if threshold is not None:
+                raise HTTPException(
+                    status_code=422,
+                    detail="`threshold` is currently not supported.",
+                )
+
         try:
             file_bytes = await serving_utils.get_raw_bytes(
                 request.image, aiohttp_session
@@ -76,15 +90,13 @@ def create_pipeline_app(
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(categories=categories, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 8 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/object_detection.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...single_model_pipeline import ObjectDetection
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -49,7 +49,10 @@ def create_pipeline_app(pipeline: ObjectDetection, app_config: AppConfig) -> Fas
     )
 
     @app.post(
-        "/object-detection", operation_id="infer", responses={422: {"model": Response}}
+        "/object-detection",
+        operation_id="infer",
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -76,15 +79,13 @@ def create_pipeline_app(pipeline: ObjectDetection, app_config: AppConfig) -> Fas
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(detectedObjects=objects, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 10 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/ocr.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...ocr import OCRPipeline
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferenceParams(BaseModel):
@@ -54,7 +54,12 @@ def create_pipeline_app(pipeline: OCRPipeline, app_config: AppConfig) -> FastAPI
         pipeline=pipeline, app_config=app_config, app_aiohttp_session=True
     )
 
-    @app.post("/ocr", operation_id="infer", responses={422: {"model": Response}})
+    @app.post(
+        "/ocr",
+        operation_id="infer",
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
+    )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
         aiohttp_session = ctx.aiohttp_session
@@ -84,15 +89,13 @@ def create_pipeline_app(pipeline: OCRPipeline, app_config: AppConfig) -> FastAPI
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(texts=texts, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/pedestrian_attribute_recognition.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...attribute_recognition import PedestrianAttributeRecPipeline
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -58,7 +58,8 @@ def create_pipeline_app(
     @app.post(
         "/pedestrian-attribute-recognition",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -88,15 +89,13 @@ def create_pipeline_app(
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(pedestrians=pedestrians, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 24 - 29
paddlex/inference/pipelines/serving/_pipeline_apps/pp_shitu_v2.py

@@ -13,10 +13,11 @@
 # limitations under the License.
 
 import asyncio
-import faiss
 import pickle
+import uuid
 from typing import Dict, List, Optional
 
+import faiss
 from fastapi import FastAPI, HTTPException
 from pydantic import BaseModel, Field
 from typing_extensions import Annotated, TypeAlias
@@ -27,7 +28,7 @@ from ...pp_shitu_v2 import ShiTuV2Pipeline
 from ..storage import create_storage
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class ImageLabelPair(BaseModel):
@@ -112,7 +113,8 @@ def create_pipeline_app(pipeline: ShiTuV2Pipeline, app_config: AppConfig) -> Fas
     @app.post(
         "/shitu-index-build",
         operation_id="buildIndex",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _build_index(
         request: BuildIndexRequest,
@@ -120,8 +122,6 @@ def create_pipeline_app(pipeline: ShiTuV2Pipeline, app_config: AppConfig) -> Fas
         pipeline = ctx.pipeline
         aiohttp_session = ctx.aiohttp_session
 
-        request_id = serving_utils.generate_request_id()
-
         try:
             images = [pair.image for pair in request.imageLabelPairs]
             file_bytes_list = await asyncio.gather(
@@ -143,7 +143,7 @@ def create_pipeline_app(pipeline: ShiTuV2Pipeline, app_config: AppConfig) -> Fas
             )
 
             index_storage = ctx.extra["index_storage"]
-            index_key = request_id
+            index_key = str(uuid.uuid4())
             index_data_bytes = await serving_utils.call_async(
                 _serialize_index_data, index_data
             )
@@ -151,21 +151,20 @@ def create_pipeline_app(pipeline: ShiTuV2Pipeline, app_config: AppConfig) -> Fas
                 index_storage.set, index_key, index_data_bytes
             )
 
-            return ResultResponse(
+            return ResultResponse[BuildIndexResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=BuildIndexResult(indexKey=index_key, idMap=index_data.id_map),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/shitu-index-add",
         operation_id="buildIndex",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _add_images_to_index(
         request: AddImagesToIndexRequest,
@@ -201,21 +200,20 @@ def create_pipeline_app(pipeline: ShiTuV2Pipeline, app_config: AppConfig) -> Fas
                 index_storage.set, request.indexKey, index_data_bytes
             )
 
-            return ResultResponse(
+            return ResultResponse[AddImagesToIndexResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=AddImagesToIndexResult(idMap=index_data.id_map),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/shitu-index-remove",
         operation_id="buildIndex",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _remove_images_from_index(
         request: RemoveImagesFromIndexRequest,
@@ -242,21 +240,20 @@ def create_pipeline_app(pipeline: ShiTuV2Pipeline, app_config: AppConfig) -> Fas
                 index_storage.set, request.indexKey, index_data_bytes
             )
 
-            return ResultResponse(
+            return ResultResponse[RemoveImagesFromIndexResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=RemoveImagesFromIndexResult(idMap=index_data.id_map),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/shitu-infer",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -305,15 +302,13 @@ def create_pipeline_app(pipeline: ShiTuV2Pipeline, app_config: AppConfig) -> Fas
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(detectedObjects=objects, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 44 - 129
paddlex/inference/pipelines/serving/_pipeline_apps/ppchatocrv3.py

@@ -12,42 +12,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import asyncio
-import os
-from typing import Awaitable, Final, List, Literal, Optional, Tuple, Union
+from typing import List, Literal, Optional, Union
 
-import numpy as np
 from fastapi import FastAPI, HTTPException
-from numpy.typing import ArrayLike
 from pydantic import BaseModel, Field
 from typing_extensions import Annotated, TypeAlias, assert_never
 
 from .....utils import logging
 from .... import results
 from ...ppchatocrv3 import PPChatOCRPipeline
-from ..storage import SupportsGetURL, Storage, create_storage
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse, DataInfo
+from ._common import ocr as ocr_common
 
-_DEFAULT_MAX_IMG_SIZE: Final[Tuple[int, int]] = (2000, 2000)
-_DEFAULT_MAX_NUM_IMGS: Final[int] = 10
 
-
-FileType: TypeAlias = Literal[0, 1]
-
-
-class InferenceParams(BaseModel):
-    maxLongSide: Optional[Annotated[int, Field(gt=0)]] = None
-
-
-class AnalyzeImagesRequest(BaseModel):
-    file: str
-    fileType: Optional[FileType] = None
+class AnalyzeImagesRequest(ocr_common.InferRequest):
     useImgOrientationCls: bool = True
-    useImgUnwrapping: bool = True
+    useImgUnwarping: bool = True
     useSealTextDet: bool = True
-    inferenceParams: Optional[InferenceParams] = None
 
 
 Point: TypeAlias = Annotated[List[int], Field(min_length=2, max_length=2)]
@@ -77,6 +60,7 @@ class VisionResult(BaseModel):
 class AnalyzeImagesResult(BaseModel):
     visionResults: List[VisionResult]
     visionInfo: dict
+    dataInfo: DataInfo
 
 
 class QianfanParams(BaseModel):
@@ -141,9 +125,9 @@ class ChatRequest(BaseModel):
 
 
 class Prompts(BaseModel):
-    ocr: str
-    table: Optional[str] = None
-    html: Optional[str] = None
+    ocr: List[str]
+    table: Optional[List[str]] = None
+    html: Optional[List[str]] = None
 
 
 class ChatResult(BaseModel):
@@ -164,62 +148,25 @@ def _llm_params_to_dict(llm_params: LLMParams) -> dict:
         assert_never(llm_params.apiType)
 
 
-def _postprocess_image(
-    img: ArrayLike,
-    request_id: str,
-    filename: str,
-    file_storage: Optional[Storage],
-) -> str:
-    key = f"{request_id}/{filename}"
-    ext = os.path.splitext(filename)[1]
-    img = np.asarray(img)
-    img_bytes = serving_utils.image_array_to_bytes(img, ext=ext)
-    if file_storage is not None:
-        file_storage.set(key, img_bytes)
-        if isinstance(file_storage, SupportsGetURL):
-            return file_storage.get_url(key)
-    return serving_utils.base64_encode(img_bytes)
-
-
 def create_pipeline_app(pipeline: PPChatOCRPipeline, app_config: AppConfig) -> FastAPI:
     app, ctx = create_app(
         pipeline=pipeline, app_config=app_config, app_aiohttp_session=True
     )
 
-    if ctx.config.extra and "file_storage" in ctx.config.extra:
-        ctx.extra["file_storage"] = create_storage(ctx.config.extra["file_storage"])
-    else:
-        ctx.extra["file_storage"] = None
-    ctx.extra.setdefault("max_img_size", _DEFAULT_MAX_IMG_SIZE)
-    ctx.extra.setdefault("max_num_imgs", _DEFAULT_MAX_NUM_IMGS)
+    ocr_common.update_app_context(ctx)
 
     @app.post(
         "/chatocr-vision",
         operation_id="analyzeImages",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _analyze_images(
         request: AnalyzeImagesRequest,
     ) -> ResultResponse[AnalyzeImagesResult]:
         pipeline = ctx.pipeline
-        aiohttp_session = ctx.aiohttp_session
-
-        request_id = serving_utils.generate_request_id()
-
-        if request.fileType is None:
-            if serving_utils.is_url(request.file):
-                try:
-                    file_type = serving_utils.infer_file_type(request.file)
-                except Exception as e:
-                    logging.exception(e)
-                    raise HTTPException(
-                        status_code=422,
-                        detail="The file type cannot be inferred from the URL. Please specify the file type explicitly.",
-                    )
-            else:
-                raise HTTPException(status_code=422, detail="Unknown file type")
-        else:
-            file_type = "PDF" if request.fileType == 0 else "IMAGE"
+
+        log_id = serving_utils.generate_log_id()
 
         if request.inferenceParams:
             max_long_side = request.inferenceParams.maxLongSide
@@ -229,53 +176,19 @@ def create_pipeline_app(pipeline: PPChatOCRPipeline, app_config: AppConfig) -> F
                     detail="`max_long_side` is currently not supported.",
                 )
 
-        try:
-            file_bytes = await serving_utils.get_raw_bytes(
-                request.file, aiohttp_session
-            )
-            images = await serving_utils.call_async(
-                serving_utils.file_to_images,
-                file_bytes,
-                file_type,
-                max_img_size=ctx.extra["max_img_size"],
-                max_num_imgs=ctx.extra["max_num_imgs"],
-            )
+        images, data_info = await ocr_common.get_images(request, ctx)
 
+        try:
             result = await pipeline.call(
                 pipeline.pipeline.visual_predict,
                 images,
                 use_doc_image_ori_cls_model=request.useImgOrientationCls,
-                use_doc_image_unwarp_model=request.useImgUnwrapping,
+                use_doc_image_unwarp_model=request.useImgUnwarping,
                 use_seal_text_det_model=request.useSealTextDet,
             )
 
             vision_results: List[VisionResult] = []
             for i, (img, item) in enumerate(zip(images, result[0])):
-                pp_img_futures: List[Awaitable] = []
-                future = serving_utils.call_async(
-                    _postprocess_image,
-                    img,
-                    request_id=request_id,
-                    filename=f"input_image_{i}.jpg",
-                    file_storage=ctx.extra["file_storage"],
-                )
-                pp_img_futures.append(future)
-                future = serving_utils.call_async(
-                    _postprocess_image,
-                    item["ocr_result"].img,
-                    request_id=request_id,
-                    filename=f"ocr_image_{i}.jpg",
-                    file_storage=ctx.extra["file_storage"],
-                )
-                pp_img_futures.append(future)
-                future = serving_utils.call_async(
-                    _postprocess_image,
-                    item["layout_result"].img,
-                    request_id=request_id,
-                    filename=f"layout_image_{i}.jpg",
-                    file_storage=ctx.extra["file_storage"],
-                )
-                pp_img_futures.append(future)
                 texts: List[Text] = []
                 for poly, text, score in zip(
                     item["ocr_result"]["dt_polys"],
@@ -287,7 +200,14 @@ def create_pipeline_app(pipeline: PPChatOCRPipeline, app_config: AppConfig) -> F
                     Table(bbox=r["layout_bbox"], html=r["html"])
                     for r in item["table_result"]
                 ]
-                input_img, ocr_img, layout_img = await asyncio.gather(*pp_img_futures)
+                input_img, ocr_img, layout_img = await ocr_common.postprocess_images(
+                    log_id=log_id,
+                    index=i,
+                    app_context=ctx,
+                    input_image=img,
+                    ocr_image=item["ocr_result"].img,
+                    layout_image=item["layout_result"].img,
+                )
                 vision_result = VisionResult(
                     texts=texts,
                     tables=tables,
@@ -297,24 +217,24 @@ def create_pipeline_app(pipeline: PPChatOCRPipeline, app_config: AppConfig) -> F
                 )
                 vision_results.append(vision_result)
 
-            return ResultResponse(
-                logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
+            return ResultResponse[AnalyzeImagesResult](
+                logId=log_id,
                 result=AnalyzeImagesResult(
                     visionResults=vision_results,
                     visionInfo=result[1],
+                    dataInfo=data_info,
                 ),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/chatocr-vector",
         operation_id="buildVectorStore",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _build_vector_store(
         request: BuildVectorStoreRequest,
@@ -336,21 +256,20 @@ def create_pipeline_app(pipeline: PPChatOCRPipeline, app_config: AppConfig) -> F
                 pipeline.pipeline.build_vector, **kwargs
             )
 
-            return ResultResponse(
+            return ResultResponse[BuildVectorStoreResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=BuildVectorStoreResult(vectorStore=result["vector"]),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/chatocr-retrieval",
         operation_id="retrieveKnowledge",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _retrieve_knowledge(
         request: RetrieveKnowledgeRequest,
@@ -371,21 +290,19 @@ def create_pipeline_app(pipeline: PPChatOCRPipeline, app_config: AppConfig) -> F
                 pipeline.pipeline.retrieval, **kwargs
             )
 
-            return ResultResponse(
+            return ResultResponse[RetrieveKnowledgeResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=RetrieveKnowledgeResult(retrievalResult=result["retrieval"]),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     @app.post(
         "/chatocr-chat",
         operation_id="chat",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
         response_model_exclude_none=True,
     )
     async def _chat(
@@ -431,15 +348,13 @@ def create_pipeline_app(pipeline: PPChatOCRPipeline, app_config: AppConfig) -> F
                 prompts=prompts,
             )
 
-            return ResultResponse(
+            return ResultResponse[ChatResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=chat_result,
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 54 - 43
paddlex/inference/pipelines/serving/_pipeline_apps/seal_recognition.py

@@ -12,27 +12,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from typing import List, Optional
+from typing import List, Type
 
 from fastapi import FastAPI, HTTPException
 from pydantic import BaseModel, Field
 from typing_extensions import Annotated, TypeAlias
 
+from ._common import ocr as ocr_common
 from .....utils import logging
 from ...seal_recognition import SealOCRPipeline
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
-
-
-class InferenceParams(BaseModel):
-    maxLongSide: Optional[Annotated[int, Field(gt=0)]] = None
-
-
-class InferRequest(BaseModel):
-    image: str
-    inferenceParams: Optional[InferenceParams] = None
+from ..models import NoResultResponse, ResultResponse, DataInfo
 
+InferRequest: Type[ocr_common.InferRequest] = ocr_common.InferRequest
 
 Point: TypeAlias = Annotated[List[int], Field(min_length=2, max_length=2)]
 Polygon: TypeAlias = Annotated[List[Point], Field(min_length=3)]
@@ -44,23 +37,35 @@ class Text(BaseModel):
     score: float
 
 
-class InferResult(BaseModel):
+class SealRecResult(BaseModel):
     texts: List[Text]
+    inputImage: str
     layoutImage: str
     ocrImage: str
 
 
+class InferResult(BaseModel):
+    sealRecResults: List[SealRecResult]
+    dataInfo: DataInfo
+
+
 def create_pipeline_app(pipeline: SealOCRPipeline, app_config: AppConfig) -> FastAPI:
     app, ctx = create_app(
         pipeline=pipeline, app_config=app_config, app_aiohttp_session=True
     )
 
+    ocr_common.update_app_context(ctx)
+
     @app.post(
-        "/seal-recognition", operation_id="infer", responses={422: {"model": Response}}
+        "/seal-recognition",
+        operation_id="infer",
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
-        aiohttp_session = ctx.aiohttp_session
+
+        log_id = serving_utils.generate_log_id()
 
         if request.inferenceParams:
             max_long_side = request.inferenceParams.maxLongSide
@@ -70,41 +75,47 @@ def create_pipeline_app(pipeline: SealOCRPipeline, app_config: AppConfig) -> Fas
                     detail="`max_long_side` is currently not supported.",
                 )
 
+        images, data_info = await ocr_common.get_images(request, ctx)
+
         try:
-            file_bytes = await serving_utils.get_raw_bytes(
-                request.image, aiohttp_session
-            )
-            image = serving_utils.image_bytes_to_array(file_bytes)
-
-            result = (await pipeline.infer(image))[0]
-
-            texts: List[Text] = []
-            for poly, text, score in zip(
-                result["ocr_result"]["dt_polys"],
-                result["ocr_result"]["rec_text"],
-                result["ocr_result"]["rec_score"],
-            ):
-                texts.append(Text(poly=poly, text=text, score=score))
-            layout_image_base64 = serving_utils.base64_encode(
-                serving_utils.image_to_bytes(result["layout_result"].img)
-            )
-            ocr_image_base64 = serving_utils.base64_encode(
-                serving_utils.image_to_bytes(result["ocr_result"].img)
-            )
+            result = await pipeline.infer(images)
+
+            seal_rec_results: List[SealRecResult] = []
+            for i, (img, item) in enumerate(zip(images, result)):
+                texts: List[Text] = []
+                for poly, text, score in zip(
+                    item["ocr_result"]["dt_polys"],
+                    item["ocr_result"]["rec_text"],
+                    item["ocr_result"]["rec_score"],
+                ):
+                    texts.append(Text(poly=poly, text=text, score=score))
+                input_img, ocr_img, layout_img = await ocr_common.postprocess_images(
+                    log_id=log_id,
+                    index=i,
+                    app_context=ctx,
+                    input_image=img,
+                    ocr_image=item["ocr_result"].img,
+                    layout_image=item["layout_result"].img,
+                )
+                seal_rec_results.append(
+                    SealRecResult(
+                        texts=texts,
+                        inputImage=input_img,
+                        layoutImage=layout_img,
+                        ocrImage=ocr_img,
+                    )
+                )
 
-            return ResultResponse(
-                logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
+            return ResultResponse[InferResult](
+                logId=log_id,
                 result=InferResult(
-                    texts=texts,
-                    layoutImage=layout_image_base64,
-                    ocrImage=ocr_image_base64,
+                    sealRecResults=seal_rec_results,
+                    dataInfo=data_info,
                 ),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/semantic_segmentation.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...single_model_pipeline import SemanticSegmentation
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -45,7 +45,8 @@ def create_pipeline_app(
     @app.post(
         "/semantic-segmentation",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -66,17 +67,15 @@ def create_pipeline_app(
                 serving_utils.image_to_bytes(result.img.convert("RGB"))
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(
                     labelMap=label_map, size=size, image=output_image_base64
                 ),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/small_object_detection.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...single_model_pipeline import SmallObjDet
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -51,7 +51,8 @@ def create_pipeline_app(pipeline: SmallObjDet, app_config: AppConfig) -> FastAPI
     @app.post(
         "/small-object-detection",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -78,15 +79,13 @@ def create_pipeline_app(pipeline: SmallObjDet, app_config: AppConfig) -> FastAPI
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(detectedObjects=objects, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 8 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/table_recognition.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...table_recognition import TableRecPipeline
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferenceParams(BaseModel):
@@ -55,7 +55,10 @@ def create_pipeline_app(pipeline: TableRecPipeline, app_config: AppConfig) -> Fa
     )
 
     @app.post(
-        "/table-recognition", operation_id="infer", responses={422: {"model": Response}}
+        "/table-recognition",
+        operation_id="infer",
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -92,10 +95,8 @@ def create_pipeline_app(pipeline: TableRecPipeline, app_config: AppConfig) -> Fa
                 serving_utils.image_to_bytes(result["ocr_result"].img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(
                     tables=tables,
                     layoutImage=layout_image_base64,
@@ -103,8 +104,8 @@ def create_pipeline_app(pipeline: TableRecPipeline, app_config: AppConfig) -> Fa
                 ),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/ts_ad.py

@@ -19,7 +19,7 @@ from .....utils import logging
 from ...single_model_pipeline import TSAd
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -38,7 +38,8 @@ def create_pipeline_app(pipeline: TSAd, app_config: AppConfig) -> FastAPI:
     @app.post(
         "/time-series-anomaly-detection",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -54,15 +55,13 @@ def create_pipeline_app(pipeline: TSAd, app_config: AppConfig) -> FastAPI:
                 serving_utils.data_frame_to_bytes(result["anomaly"])
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(csv=output_csv),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/ts_cls.py

@@ -19,7 +19,7 @@ from .....utils import logging
 from ...single_model_pipeline import TSCls
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -39,7 +39,8 @@ def create_pipeline_app(pipeline: TSCls, app_config: AppConfig) -> FastAPI:
     @app.post(
         "/time-series-classification",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -54,15 +55,13 @@ def create_pipeline_app(pipeline: TSCls, app_config: AppConfig) -> FastAPI:
             label = str(result["classification"].at[0, "classid"])
             score = float(result["classification"].at[0, "score"])
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(label=label, score=score),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/ts_fc.py

@@ -19,7 +19,7 @@ from .....utils import logging
 from ...single_model_pipeline import TSFc
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -38,7 +38,8 @@ def create_pipeline_app(pipeline: TSFc, app_config: AppConfig) -> FastAPI:
     @app.post(
         "/time-series-forecasting",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -54,15 +55,13 @@ def create_pipeline_app(pipeline: TSFc, app_config: AppConfig) -> FastAPI:
                 serving_utils.data_frame_to_bytes(result["forecast"])
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(csv=output_csv),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 6 - 7
paddlex/inference/pipelines/serving/_pipeline_apps/vehicle_attribute_recognition.py

@@ -22,7 +22,7 @@ from .....utils import logging
 from ...attribute_recognition import VehicleAttributeRecPipeline
 from .. import utils as serving_utils
 from ..app import AppConfig, create_app
-from ..models import Response, ResultResponse
+from ..models import NoResultResponse, ResultResponse
 
 
 class InferRequest(BaseModel):
@@ -58,7 +58,8 @@ def create_pipeline_app(
     @app.post(
         "/vehicle-attribute-recognition",
         operation_id="infer",
-        responses={422: {"model": Response}},
+        responses={422: {"model": NoResultResponse}},
+        response_model_exclude_none=True,
     )
     async def _infer(request: InferRequest) -> ResultResponse[InferResult]:
         pipeline = ctx.pipeline
@@ -88,15 +89,13 @@ def create_pipeline_app(
                 serving_utils.image_to_bytes(result.img)
             )
 
-            return ResultResponse(
+            return ResultResponse[InferResult](
                 logId=serving_utils.generate_log_id(),
-                errorCode=0,
-                errorMsg="Success",
                 result=InferResult(vehicles=vehicles, image=output_image_base64),
             )
 
-        except Exception as e:
-            logging.exception(e)
+        except Exception:
+            logging.exception("Unexpected exception")
             raise HTTPException(status_code=500, detail="Internal server error")
 
     return app

+ 9 - 6
paddlex/inference/pipelines/serving/app.py

@@ -22,6 +22,7 @@ from typing import (
     Dict,
     Generic,
     List,
+    Mapping,
     Optional,
     Tuple,
     TypeVar,
@@ -37,7 +38,7 @@ from starlette.exceptions import HTTPException
 from typing_extensions import Final, ParamSpec
 
 from ..base import BasePipeline
-from .models import Response
+from .models import NoResultResponse
 from .utils import call_async, generate_log_id
 
 SERVING_CONFIG_KEY: Final[str] = "Serving"
@@ -108,7 +109,7 @@ class AppContext(Generic[_PipelineT]):
         self._aiohttp_session = val
 
 
-def create_app_config(pipeline_config: Dict[str, Any], **kwargs: Any) -> AppConfig:
+def create_app_config(pipeline_config: Mapping[str, Any], **kwargs: Any) -> AppConfig:
     app_config = pipeline_config.get(SERVING_CONFIG_KEY, {})
     app_config.update(kwargs)
     return AppConfig.model_validate(app_config)
@@ -134,15 +135,17 @@ def create_app(
     app.state.context = ctx
 
     @app.get("/health", operation_id="checkHealth")
-    async def _check_health() -> Response:
-        return Response(logId=generate_log_id(), errorCode=0, errorMsg="Healthy")
+    async def _check_health() -> NoResultResponse:
+        return NoResultResponse(
+            logId=generate_log_id(), errorCode=0, errorMsg="Healthy"
+        )
 
     @app.exception_handler(RequestValidationError)
     async def _validation_exception_handler(
         request: fastapi.Request, exc: RequestValidationError
     ) -> JSONResponse:
         json_compatible_data = jsonable_encoder(
-            Response(
+            NoResultResponse(
                 logId=generate_log_id(),
                 errorCode=422,
                 errorMsg=json.dumps(exc.errors()),
@@ -155,7 +158,7 @@ def create_app(
         request: fastapi.Request, exc: HTTPException
     ) -> JSONResponse:
         json_compatible_data = jsonable_encoder(
-            Response(
+            NoResultResponse(
                 logId=generate_log_id(), errorCode=exc.status_code, errorMsg=exc.detail
             )
         )

+ 31 - 3
paddlex/inference/pipelines/serving/models.py

@@ -12,12 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from typing import Generic, TypeVar
+from typing import Generic, TypeVar, Union, Literal, List, Optional
 
 from pydantic import BaseModel
 
 
-class Response(BaseModel):
+class NoResultResponse(BaseModel):
     logId: str
     errorCode: int
     errorMsg: str
@@ -26,5 +26,33 @@ class Response(BaseModel):
 ResultT = TypeVar("ResultT", bound=BaseModel)
 
 
-class ResultResponse(Response, Generic[ResultT]):
+class ResultResponse(BaseModel, Generic[ResultT]):
+    logId: str
+    errorCode: Literal[0] = 0
+    errorMsg: Literal["Success"] = "Success"
     result: ResultT
+
+
+Response = Union[ResultResponse, NoResultResponse]
+
+
+class ImageInfo(BaseModel):
+    width: int
+    height: int
+
+
+class PDFPageInfo(BaseModel):
+    width: int
+    height: int
+
+
+class PDFInfo(BaseModel):
+    numPages: int
+    pages: List[PDFPageInfo]
+
+
+class DataInfo(BaseModel):
+    image: Optional[ImageInfo] = None
+    pdf: Optional[PDFInfo] = None
+
+    # TODO: Validate that only one field is set

+ 55 - 28
paddlex/inference/pipelines/serving/utils.py

@@ -19,7 +19,18 @@ import os
 import re
 import uuid
 from functools import partial
-from typing import Awaitable, Callable, List, Literal, Optional, TypeVar, Final, Tuple
+from typing import (
+    Awaitable,
+    Callable,
+    List,
+    Literal,
+    Optional,
+    TypeVar,
+    Final,
+    Tuple,
+    overload,
+    Union,
+)
 from urllib.parse import parse_qs, urlparse
 
 import aiohttp
@@ -31,6 +42,8 @@ import yarl
 from PIL import Image
 from typing_extensions import ParamSpec, assert_never
 
+from .models import ImageInfo, PDFInfo, PDFPageInfo
+
 FileType = Literal["IMAGE", "PDF"]
 
 _P = ParamSpec("_P")
@@ -41,10 +54,6 @@ def generate_log_id() -> str:
     return str(uuid.uuid4())
 
 
-def generate_request_id() -> str:
-    return str(uuid.uuid4())
-
-
 def is_url(s: str) -> bool:
     if not (s.startswith("http://") or s.startswith("https://")):
         # Quick rejection
@@ -112,7 +121,7 @@ def image_to_bytes(image: Image.Image, format: str = "JPEG") -> bytes:
     return img_bytes
 
 
-def image_array_to_bytes(image: np.ndarray, ext: str = ".jpg") -> str:
+def image_array_to_bytes(image: np.ndarray, ext: str = ".jpg") -> bytes:
     image = cv2.imencode(ext, image)[1]
     return image.tobytes()
 
@@ -132,10 +141,10 @@ def base64_encode(data: bytes) -> str:
 
 
 def read_pdf(
-    bytes_: bytes, resize: bool = False, max_num_imgs: Optional[int] = None
-) -> List[np.ndarray]:
+    bytes_: bytes, max_num_imgs: Optional[int] = None
+) -> Tuple[List[np.ndarray], PDFInfo]:
     images: List[np.ndarray] = []
-    img_size = None
+    page_info_list: List[PDFPageInfo] = []
     with fitz.open("pdf", bytes_) as doc:
         for page in doc:
             if max_num_imgs is not None and len(images) >= max_num_imgs:
@@ -149,37 +158,55 @@ def read_pdf(
                 pixmap.h, pixmap.w, pixmap.n
             )
             image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
-            if resize:
-                if img_size is None:
-                    img_size = (image.shape[1], image.shape[0])
-                else:
-                    if (image.shape[1], image.shape[0]) != img_size:
-                        image = cv2.resize(image, img_size)
             images.append(image)
-    return images
+            page_info = PDFPageInfo(
+                width=pixmap.w,
+                height=pixmap.h,
+            )
+            page_info_list.append(page_info)
+    pdf_info = PDFInfo(
+        numPages=len(page_info_list),
+        pages=page_info_list,
+    )
+    return images, pdf_info
+
+
+@overload
+def file_to_images(
+    file_bytes: bytes,
+    file_type: Literal["IMAGE"],
+    *,
+    max_num_imgs: Optional[int] = ...,
+) -> Tuple[List[np.ndarray], ImageInfo]: ...
+
+
+@overload
+def file_to_images(
+    file_bytes: bytes,
+    file_type: Literal["PDF"],
+    *,
+    max_num_imgs: Optional[int] = ...,
+) -> Tuple[List[np.ndarray], PDFInfo]: ...
 
 
 def file_to_images(
     file_bytes: bytes,
     file_type: Literal["IMAGE", "PDF"],
     *,
-    max_img_size: Tuple[int, int],
-    max_num_imgs: int,
-) -> List[np.ndarray]:
+    max_num_imgs: Optional[int] = None,
+) -> Tuple[List[np.ndarray], Union[ImageInfo, PDFInfo]]:
     if file_type == "IMAGE":
         images = [image_bytes_to_array(file_bytes)]
+        data_info = get_image_info(images[0])
     elif file_type == "PDF":
-        images = read_pdf(file_bytes, resize=True, max_num_imgs=max_num_imgs)
+        images, data_info = read_pdf(file_bytes, max_num_imgs=max_num_imgs)
     else:
         assert_never(file_type)
-    h, w = images[0].shape[0:2]
-    if w > max_img_size[1] or h > max_img_size[0]:
-        if w / h > max_img_size[0] / max_img_size[1]:
-            factor = max_img_size[0] / w
-        else:
-            factor = max_img_size[1] / h
-        images = [cv2.resize(img, (int(factor * w), int(factor * h))) for img in images]
-    return images
+    return images, data_info
+
+
+def get_image_info(image: np.ndarray) -> ImageInfo:
+    return ImageInfo(width=image.shape[1], height=image.shape[0])
 
 
 def call_async(

+ 1 - 1
paddlex/serving_requirements.txt

@@ -1,5 +1,5 @@
 aiohttp>=3.9
-bce-python-sdk>=0.8
+bce-python-sdk>=0.9
 fastapi>=0.110
 pydantic>=2
 starlette>=0.36