guwenzi-tools 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- guwenzi_tools-0.2.0/LICENSE +21 -0
- guwenzi_tools-0.2.0/MANIFEST.in +4 -0
- guwenzi_tools-0.2.0/PKG-INFO +203 -0
- guwenzi_tools-0.2.0/PUBLISHING.md +66 -0
- guwenzi_tools-0.2.0/README.md +178 -0
- guwenzi_tools-0.2.0/pyproject.toml +35 -0
- guwenzi_tools-0.2.0/scripts/audit_dist.py +39 -0
- guwenzi_tools-0.2.0/scripts/integration_check.py +78 -0
- guwenzi_tools-0.2.0/setup.cfg +4 -0
- guwenzi_tools-0.2.0/src/guwenzi_tools.egg-info/PKG-INFO +203 -0
- guwenzi_tools-0.2.0/src/guwenzi_tools.egg-info/SOURCES.txt +27 -0
- guwenzi_tools-0.2.0/src/guwenzi_tools.egg-info/dependency_links.txt +1 -0
- guwenzi_tools-0.2.0/src/guwenzi_tools.egg-info/entry_points.txt +2 -0
- guwenzi_tools-0.2.0/src/guwenzi_tools.egg-info/requires.txt +10 -0
- guwenzi_tools-0.2.0/src/guwenzi_tools.egg-info/top_level.txt +1 -0
- guwenzi_tools-0.2.0/src/gwz_client/__init__.py +8 -0
- guwenzi_tools-0.2.0/src/gwz_client/cli.py +320 -0
- guwenzi_tools-0.2.0/src/gwz_client/client.py +235 -0
- guwenzi_tools-0.2.0/src/gwz_client/config.py +111 -0
- guwenzi_tools-0.2.0/src/gwz_client/data/skill/SKILL.md +75 -0
- guwenzi_tools-0.2.0/src/gwz_client/data/tools-v1.json +376 -0
- guwenzi_tools-0.2.0/src/gwz_client/errors.py +13 -0
- guwenzi_tools-0.2.0/src/gwz_client/evidence.py +207 -0
- guwenzi_tools-0.2.0/src/gwz_client/files.py +31 -0
- guwenzi_tools-0.2.0/src/gwz_client/fonts.py +257 -0
- guwenzi_tools-0.2.0/src/gwz_client/typography.py +76 -0
- guwenzi_tools-0.2.0/src/gwz_client/workflow.py +221 -0
- guwenzi_tools-0.2.0/tests/test_client.py +229 -0
- guwenzi_tools-0.2.0/tests/test_fonts.py +100 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Guwenzi contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: guwenzi-tools
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI and Python client for evidence-preserving historical-document and glyph tools
|
|
5
|
+
Author: Guwenzi contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: ocr,paleography,glyph,cli,multimodal
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: httpx<1,>=0.28
|
|
17
|
+
Requires-Dist: filelock<4,>=3.15
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: build<2,>=1.2; extra == "dev"
|
|
20
|
+
Requires-Dist: twine<7,>=6; extra == "dev"
|
|
21
|
+
Provides-Extra: fonts
|
|
22
|
+
Requires-Dist: fonttools<5,>=4.60; extra == "fonts"
|
|
23
|
+
Requires-Dist: skia-pathops<1,>=0.8; extra == "fonts"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# guwenzi-tools
|
|
27
|
+
|
|
28
|
+
A lightweight CLI and Python client for a remote **Guwenzi** historical-document and glyph service. Supports external multimodal agents that can run commands and view images. Python 3.10+; Linux, macOS and Windows. Client license: MIT.
|
|
29
|
+
|
|
30
|
+
安装包只包含客户端、工具schema和agent说明。模型、字库、文献和访问凭据留在各自的部署环境里。服务端需要实现 `guwenzi.tools.v1`;安装客户端不会自动获得公共推理服务,也不会在本机下载或加载Qwen、DINO、YOLO、torch或MLX。
|
|
31
|
+
|
|
32
|
+
## Install and connect
|
|
33
|
+
|
|
34
|
+
Once the project is published to PyPI:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
python -m pip install guwenzi-tools
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Before publication, install the release wheel or this source directory:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
python -m pip install /path/to/guwenzi_tools-0.2.0-py3-none-any.whl
|
|
44
|
+
# or, from this directory
|
|
45
|
+
python -m pip install .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Configure the endpoint once. Enter the service token at the hidden prompt:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
guwenzi-tools configure --endpoint https://your-service.example/api/predict/guwenzi_tools
|
|
52
|
+
guwenzi-tools doctor --quick
|
|
53
|
+
guwenzi-tools doctor
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
EAS uses raw Authorization by default. For a standalone server with Bearer authentication add `--auth bearer`. HTTPS is the default requirement; explicitly use `--allow-http` for a service that only has HTTP. Loopback HTTP works for local testing. The complete service URL includes its application prefix; do not append `/health` or `/v1` when configuring it.
|
|
57
|
+
|
|
58
|
+
For agents and CI, configure a reference to an environment variable instead of storing its value:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
guwenzi-tools configure --endpoint https://your-service.example/api/predict/guwenzi_tools --token-env GWZ_REMOTE_TOKEN
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Provide that environment variable through your agent's secret settings. `--token-stdin` is another option. `config show` redacts saved tokens. Multiple profiles are supported with `--profile NAME`; `config use NAME` changes the default, and the first configured profile is selected automatically. The config location can be changed with `--config FILE` or `GWZ_CLIENT_CONFIG`. POSIX credential files are written with mode0600.
|
|
65
|
+
|
|
66
|
+
The existing `GWZ_REMOTE_URL`, `GWZ_REMOTE_TOKEN`, `GWZ_REMOTE_AUTH` environment variables and `--remote URL` alias remain supported. Explicit endpoint overrides do not inherit a different server's stored token.
|
|
67
|
+
|
|
68
|
+
## Document workflow
|
|
69
|
+
|
|
70
|
+
Start with one page, inspect the evidence, and continue according to the reading task:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
guwenzi-tools document open paper.pdf
|
|
74
|
+
guwenzi-tools page prepare DOCUMENT_ID 1 --output work/page-1
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For a terminal tool with a short waiting limit, submit and collect separately:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
guwenzi-tools page prepare DOCUMENT_ID 1 --output work/page-1 --detach
|
|
81
|
+
guwenzi-tools status JOB_ID --wait --output work/page-1
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The second command downloads the completed page's evidence and prints a compact summary. It can be repeated after an interruption. Crop, line splitting and glyph search/resolve also support `--detach`.
|
|
85
|
+
|
|
86
|
+
Or prepare selected pages in one resumable operation:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
guwenzi-tools document prepare paper.pdf --pages 1-3 --output work/paper
|
|
90
|
+
guwenzi-tools document prepare paper.pdf --pages 1-3 --output work/paper --resume
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`document prepare` defaults to all pages if `--pages` is omitted. Mode/DPI, endpoint, page range and source hash must agree when resuming. Preparation is sequential, matching the small server's one-heavy-task limit. A complete evidence preparation is not a completed OCR transcription.
|
|
94
|
+
|
|
95
|
+
Output folders contain:
|
|
96
|
+
|
|
97
|
+
| File | Purpose |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `brief.json` | Compact navigation and original image paths |
|
|
100
|
+
| `result.json` | Full server response, with source text and unresolved status |
|
|
101
|
+
| `materials.json` | Original/viewing-derivative files and SHA256 verification |
|
|
102
|
+
| `index.html` | Local visual review page; image links open original files |
|
|
103
|
+
| `transcriptions.draft.json` | Per-block requests to fill after reading the images |
|
|
104
|
+
| `assets/` | Exact original bytes and separately labelled enlarged views |
|
|
105
|
+
|
|
106
|
+
Native PDFs retain source-layer text and original glyph evidence. Scans use remote YOLO layout and projection lines/slots, then the calling multimodal model supplies text. Word/PPT are converted by the server. The CLI itself does not infer the reading of an image.
|
|
107
|
+
|
|
108
|
+
## Uncertain glyphs
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
guwenzi-tools line split LINE_ASSET_ID --output work/line
|
|
112
|
+
guwenzi-tools crop LINE_ASSET_ID --box 10 0 60 80 --output work/crop
|
|
113
|
+
guwenzi-tools glyph search CROP_ASSET_ID --output work/search
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
View the query and candidate originals returned by the search. Select a matching candidate using its request-scoped ID:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
guwenzi-tools glyph select SEARCH_ID c3 --output work/binding.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The choice of `c3` is an example, not a default decision. If no candidate matches, expand once or register the unresolved original:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
guwenzi-tools glyph search CROP_ASSET_ID --expand-search SEARCH_ID --output work/expanded
|
|
126
|
+
guwenzi-tools glyph register CROP_ASSET_ID --output work/unresolved.json
|
|
127
|
+
guwenzi-tools glyph resolve PERMANENT_CODE --output work/identity
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Projection proposes boundaries. Crop boxes always use original-image pixels. `gwz:` codes identify a specific original/source asset; similarity, selection and a valid code do not prove a Unicode reading. Source identities and original forms are preserved.
|
|
131
|
+
|
|
132
|
+
## Transcribe and export
|
|
133
|
+
|
|
134
|
+
Fill `transcriptions.draft.json` with text and structured glyph references, then:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
guwenzi-tools transcribe work/page-1/transcriptions.draft.json
|
|
138
|
+
guwenzi-tools document result DOCUMENT_ID --offset 0 --limit 20
|
|
139
|
+
guwenzi-tools document export DOCUMENT_ID --output work/export
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use `--skip-empty` only when intentionally submitting a partially filled draft. Every `part` has exactly one string field: `text`, `binding_id`, `glyph_code`, or `asset_id`. Bindings must belong to the selected block or its recorded crops/slots; the server enforces the association. The exported JSON keeps original source text, transcriptions, figures/tables and verification status. JSON preserves codepoints exactly; it performs no Unicode normalization or traditional/simplified conversion. Markdown is a readable derivative and references the preserved image files.
|
|
143
|
+
|
|
144
|
+
Export saves assets referenced by the v1 API and the archived original document. It does not claim to enumerate every intermediate file kept inside the server. For large exports, consider `--no-images` first or a page-specific evidence packet.
|
|
145
|
+
|
|
146
|
+
## Agents and low-level access
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
guwenzi-tools agent guide --plain
|
|
150
|
+
guwenzi-tools agent install-skill --directory /your-agent/skills/guwenzi-remote
|
|
151
|
+
guwenzi-tools tools
|
|
152
|
+
guwenzi-tools tools --server
|
|
153
|
+
guwenzi-tools call glyph_search --json @request.json
|
|
154
|
+
guwenzi-tools call page_prepare --json @request.json --detach
|
|
155
|
+
guwenzi-tools status JOB_ID --wait
|
|
156
|
+
guwenzi-tools asset ASSET_ID --output original.png
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Convenience commands output one JSON envelope on stdout and progress on stderr. Full page/search responses are stored locally while summaries point the agent to image files; image base64 is not printed. `call` and `status` retain the v1 job envelope. Tasks already submitted continue on the server if the CLI stops waiting. Receipts retain job IDs without credentials. An uncertain POST outcome is not automatically repeated; GET failures and explicit429 rejections have bounded retries.
|
|
160
|
+
|
|
161
|
+
Any multimodal agent with shell access and an image-viewing function can use the same CLI. Hosted chat interfaces that cannot run commands need an application-side tool adapter; installing a Python package alone does not give a hosted model filesystem or shell access. No MCP connection or specific model provider is required.
|
|
162
|
+
|
|
163
|
+
## Python SDK
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from gwz_client import Client
|
|
167
|
+
from gwz_client.workflow import Preparation
|
|
168
|
+
|
|
169
|
+
with Client.from_config() as client:
|
|
170
|
+
print(client.health())
|
|
171
|
+
result = Preparation(client, "work/paper").run("paper.pdf", page_spec="1")
|
|
172
|
+
print(result["document_id"])
|
|
173
|
+
job = client.call("document_result", {"document_id": result["document_id"], "limit": 20})
|
|
174
|
+
print(job["result"])
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Credentials can also be supplied to `Client(endpoint, token, auth="raw")` by your application's secret store. The SDK and CLI share the same transport and integrity checks.
|
|
178
|
+
|
|
179
|
+
## Distribution and service access
|
|
180
|
+
|
|
181
|
+
PyPI distributes the client, not an inference entitlement. Each deployment owner provides an endpoint and access credentials separately. The current v1 service has one EAS access domain and a shared document store; profiles are client connection settings, not server-side tenant isolation. A public multi-user SaaS would need separate identity, quotas and data isolation on the service side.
|
|
182
|
+
|
|
183
|
+
This repository can build a wheel and source distribution with `python -m build`, and validate them with `python -m twine check dist/*`. Publication is a separate owner action. See `PUBLISHING.md` in the source distribution for TestPyPI/PyPI release steps. This MIT license covers the client only; server dependencies, model weights and external glyph datasets have their own licenses.
|
|
184
|
+
|
|
185
|
+
## Inline glyphs and real fonts
|
|
186
|
+
|
|
187
|
+
Page preparation and document export write `reading.html`: permanent gwz markers display as inline glyph images, with links to the enlarged originals. Long IDs remain in the evidence JSON. This view can be printed even when no usable font outline exists.
|
|
188
|
+
|
|
189
|
+
For a native PDF packet with preserved `.path` programs, install the optional font dependencies and export an ordinary OpenType CFF font:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
python -m pip install 'guwenzi-tools[fonts]'
|
|
193
|
+
guwenzi-tools font build --packet work/page-1 --output work/font
|
|
194
|
+
guwenzi-tools font encode text.txt --map work/font/glyph-map.json --output work/typeset.txt
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Install the generated `.otf` in your word processor, or explicitly embed it in your typesetting tool. `font-preview.html` embeds it for local browser viewing. `glyph-map.json` records its SHA256, font-local PUA characters, gwz identities, original path hashes and geometric transforms. Keep the font and mapping with the document. A gwz identifier alone is not an installed font character; PUA has meaning only with the matching font.
|
|
198
|
+
|
|
199
|
+
Supported original fill paths retain Bezier curves; independent fills are unioned before font encoding. Unsupported paint/clipping operations and conflicting outlines are reported, not silently traced. CFF has finite coordinate precision, and the export normalizes the glyph into a common em box; it does not recover the original font's complete metrics. Original evidence remains unchanged.
|
|
200
|
+
|
|
201
|
+
For a **printed bitmap** without usable original outlines, a multimodal agent can reuse the existing companion `guwenzi-glyph compose` CLI to write KAGE programs, render, inspect, revise and export fonts. This is explicitly a model reconstruction, not the original shape. Handwriting, rubbings and uncertain forms remain images. The companion CLI, Node, KAGE engine and GlyphWiki dump are separate from this lightweight client; `pip install guwenzi-tools` does not install them. The client itself does not infer a KAGE program from a bitmap.
|
|
202
|
+
|
|
203
|
+
`glyph search` deliberately shows images and request-scoped candidate IDs before revealing labels. `glyph select` returns the complete selected identity; `glyph resolve` saves it in `result.json` and includes an `identity` summary on stdout: source codepoints, font/source hashes, version, glyph ID/name and available source aliases/relations. `reading_status=no_confirmed_reading_in_v1` means the API does not provide a verified scholarly reading. A source font can borrow an unassigned, private-use or unrelated codepoint; neither a `U+...` label nor a glyph name confirms Unicode identity. `source_material_in_bundle=false` also means the metadata is available but the original font program is not in that deployed bundle.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Publishing the lightweight client
|
|
2
|
+
|
|
3
|
+
`guwenzi-tools` is a normal Python distribution, even when the inference service is hosted on your own server. PyPI hosts the CLI/SDK code; the server endpoint and credentials are configured by the installer. PyPI does not run the server or distribute its model weights. This client's license is MIT; it does not license the server image, models or glyph datasets.
|
|
4
|
+
|
|
5
|
+
The client source is this directory (`tools/guwenzi-client`), not the historical server-side package in `tools/guwenzi-service`. The distribution contains only `gwz_client`, the public tool schemas, the agent skill and documentation. Do not add endpoint credentials, `.ossutilconfig`, local profiles, workspaces or research data to it.
|
|
6
|
+
|
|
7
|
+
## 1. Build and inspect locally
|
|
8
|
+
|
|
9
|
+
Use Python3.10 or newer, in an isolated environment:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
python -m pip install -e '.[dev]'
|
|
13
|
+
python -m unittest discover -s tests -v
|
|
14
|
+
python -m build --outdir dist
|
|
15
|
+
python -m twine check dist/*.whl dist/*.tar.gz
|
|
16
|
+
python scripts/audit_dist.py dist
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Test installation into a new virtual environment and run `guwenzi-tools --version`, `tools`, `agent guide` and a real service call. The release is compatible with the existing CPU service protocol v1; it does not require re-uploading its Docker image.
|
|
20
|
+
|
|
21
|
+
## 2. Create the publishing account
|
|
22
|
+
|
|
23
|
+
Create a PyPI account, verify its email address and enable two-factor authentication. TestPyPI is a separate service/account. The name `guwenzi-tools` must still be available when you first upload; a successful lookup returning404 is not a reservation. Recheck it before publishing. If someone else owns it, choose a different distribution name in pyproject.toml (the console command can remain guwenzi-tools).
|
|
24
|
+
|
|
25
|
+
For manual upload, create a PyPI API token in account settings. On the first upload there is no existing project to scope a token to, so use the available account-level token option for that initial creation, then use a project-scoped token for later releases. The PyPI token is separate from the EAS service token and Alibaba AccessKey. Supply publishing credentials to Twine locally; do not put them in the package.
|
|
26
|
+
|
|
27
|
+
## 3. Upload to TestPyPI, then verify installation
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
python -m twine upload --repository testpypi --username __token__ dist/*.whl dist/*.tar.gz
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Enter the TestPyPI API token at the password prompt. Install ordinary dependencies from the normal PyPI index first, then fetch only this package from TestPyPI:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
python -m pip install 'httpx>=0.28,<1' 'filelock>=3.15,<4'
|
|
37
|
+
python -m pip install --index-url https://test.pypi.org/simple/ --no-deps guwenzi-tools==0.2.0
|
|
38
|
+
guwenzi-tools --version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Configure a server profile and exercise a small document. Publishing a client to TestPyPI does not copy your EAS endpoint or give test users server access.
|
|
42
|
+
|
|
43
|
+
## 4. Publish the reviewed artefacts to PyPI
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
python -m twine upload --username __token__ dist/*.whl dist/*.tar.gz
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use the PyPI token, not the TestPyPI token. A package/version filename cannot simply be overwritten later: bump the version for subsequent changes. Once uploaded successfully, users can run:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
python -m pip install guwenzi-tools
|
|
53
|
+
guwenzi-tools configure --endpoint https://their-service.example/api/predict/guwenzi_tools
|
|
54
|
+
guwenzi-tools doctor --quick
|
|
55
|
+
guwenzi-tools agent install-skill --directory /their-agent/skills/guwenzi-remote
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Actual PyPI/TestPyPI upload is an explicit release action. The build/audit scripts do not upload anything automatically.
|
|
59
|
+
|
|
60
|
+
## Automated releases later
|
|
61
|
+
|
|
62
|
+
When a source repository and CI are available, PyPI Trusted Publishing can exchange a CI identity for a short-lived publishing token. Configure the exact repository/workflow/environment under the project (or a pending publisher), build once, run tests, inspect the artefacts, then publish through the protected release workflow. A source repository is not required for the first manual Twine release.
|
|
63
|
+
|
|
64
|
+
For a public hosted service, separate client distribution from user provisioning. The current v1 EAS token gives access to one shared service/data domain; a public multi-user offering still needs independent identities, data isolation, quotas and service-side limits. The CLI's profiles are not that isolation layer.
|
|
65
|
+
|
|
66
|
+
References: [Python packaging tutorial](https://packaging.python.org/en/latest/tutorials/packaging-projects/), [TestPyPI guide](https://packaging.python.org/en/latest/guides/using-testpypi/), [PyPI account/token help](https://pypi.org/help/), [Trusted Publishing](https://docs.pypi.org/trusted-publishers/using-a-publisher/), [Twine](https://twine.readthedocs.io/en/stable/).
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# guwenzi-tools
|
|
2
|
+
|
|
3
|
+
A lightweight CLI and Python client for a remote **Guwenzi** historical-document and glyph service. Supports external multimodal agents that can run commands and view images. Python 3.10+; Linux, macOS and Windows. Client license: MIT.
|
|
4
|
+
|
|
5
|
+
安装包只包含客户端、工具schema和agent说明。模型、字库、文献和访问凭据留在各自的部署环境里。服务端需要实现 `guwenzi.tools.v1`;安装客户端不会自动获得公共推理服务,也不会在本机下载或加载Qwen、DINO、YOLO、torch或MLX。
|
|
6
|
+
|
|
7
|
+
## Install and connect
|
|
8
|
+
|
|
9
|
+
Once the project is published to PyPI:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
python -m pip install guwenzi-tools
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Before publication, install the release wheel or this source directory:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
python -m pip install /path/to/guwenzi_tools-0.2.0-py3-none-any.whl
|
|
19
|
+
# or, from this directory
|
|
20
|
+
python -m pip install .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Configure the endpoint once. Enter the service token at the hidden prompt:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
guwenzi-tools configure --endpoint https://your-service.example/api/predict/guwenzi_tools
|
|
27
|
+
guwenzi-tools doctor --quick
|
|
28
|
+
guwenzi-tools doctor
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
EAS uses raw Authorization by default. For a standalone server with Bearer authentication add `--auth bearer`. HTTPS is the default requirement; explicitly use `--allow-http` for a service that only has HTTP. Loopback HTTP works for local testing. The complete service URL includes its application prefix; do not append `/health` or `/v1` when configuring it.
|
|
32
|
+
|
|
33
|
+
For agents and CI, configure a reference to an environment variable instead of storing its value:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
guwenzi-tools configure --endpoint https://your-service.example/api/predict/guwenzi_tools --token-env GWZ_REMOTE_TOKEN
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Provide that environment variable through your agent's secret settings. `--token-stdin` is another option. `config show` redacts saved tokens. Multiple profiles are supported with `--profile NAME`; `config use NAME` changes the default, and the first configured profile is selected automatically. The config location can be changed with `--config FILE` or `GWZ_CLIENT_CONFIG`. POSIX credential files are written with mode0600.
|
|
40
|
+
|
|
41
|
+
The existing `GWZ_REMOTE_URL`, `GWZ_REMOTE_TOKEN`, `GWZ_REMOTE_AUTH` environment variables and `--remote URL` alias remain supported. Explicit endpoint overrides do not inherit a different server's stored token.
|
|
42
|
+
|
|
43
|
+
## Document workflow
|
|
44
|
+
|
|
45
|
+
Start with one page, inspect the evidence, and continue according to the reading task:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
guwenzi-tools document open paper.pdf
|
|
49
|
+
guwenzi-tools page prepare DOCUMENT_ID 1 --output work/page-1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For a terminal tool with a short waiting limit, submit and collect separately:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
guwenzi-tools page prepare DOCUMENT_ID 1 --output work/page-1 --detach
|
|
56
|
+
guwenzi-tools status JOB_ID --wait --output work/page-1
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The second command downloads the completed page's evidence and prints a compact summary. It can be repeated after an interruption. Crop, line splitting and glyph search/resolve also support `--detach`.
|
|
60
|
+
|
|
61
|
+
Or prepare selected pages in one resumable operation:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
guwenzi-tools document prepare paper.pdf --pages 1-3 --output work/paper
|
|
65
|
+
guwenzi-tools document prepare paper.pdf --pages 1-3 --output work/paper --resume
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`document prepare` defaults to all pages if `--pages` is omitted. Mode/DPI, endpoint, page range and source hash must agree when resuming. Preparation is sequential, matching the small server's one-heavy-task limit. A complete evidence preparation is not a completed OCR transcription.
|
|
69
|
+
|
|
70
|
+
Output folders contain:
|
|
71
|
+
|
|
72
|
+
| File | Purpose |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `brief.json` | Compact navigation and original image paths |
|
|
75
|
+
| `result.json` | Full server response, with source text and unresolved status |
|
|
76
|
+
| `materials.json` | Original/viewing-derivative files and SHA256 verification |
|
|
77
|
+
| `index.html` | Local visual review page; image links open original files |
|
|
78
|
+
| `transcriptions.draft.json` | Per-block requests to fill after reading the images |
|
|
79
|
+
| `assets/` | Exact original bytes and separately labelled enlarged views |
|
|
80
|
+
|
|
81
|
+
Native PDFs retain source-layer text and original glyph evidence. Scans use remote YOLO layout and projection lines/slots, then the calling multimodal model supplies text. Word/PPT are converted by the server. The CLI itself does not infer the reading of an image.
|
|
82
|
+
|
|
83
|
+
## Uncertain glyphs
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
guwenzi-tools line split LINE_ASSET_ID --output work/line
|
|
87
|
+
guwenzi-tools crop LINE_ASSET_ID --box 10 0 60 80 --output work/crop
|
|
88
|
+
guwenzi-tools glyph search CROP_ASSET_ID --output work/search
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
View the query and candidate originals returned by the search. Select a matching candidate using its request-scoped ID:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
guwenzi-tools glyph select SEARCH_ID c3 --output work/binding.json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The choice of `c3` is an example, not a default decision. If no candidate matches, expand once or register the unresolved original:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
guwenzi-tools glyph search CROP_ASSET_ID --expand-search SEARCH_ID --output work/expanded
|
|
101
|
+
guwenzi-tools glyph register CROP_ASSET_ID --output work/unresolved.json
|
|
102
|
+
guwenzi-tools glyph resolve PERMANENT_CODE --output work/identity
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Projection proposes boundaries. Crop boxes always use original-image pixels. `gwz:` codes identify a specific original/source asset; similarity, selection and a valid code do not prove a Unicode reading. Source identities and original forms are preserved.
|
|
106
|
+
|
|
107
|
+
## Transcribe and export
|
|
108
|
+
|
|
109
|
+
Fill `transcriptions.draft.json` with text and structured glyph references, then:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
guwenzi-tools transcribe work/page-1/transcriptions.draft.json
|
|
113
|
+
guwenzi-tools document result DOCUMENT_ID --offset 0 --limit 20
|
|
114
|
+
guwenzi-tools document export DOCUMENT_ID --output work/export
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use `--skip-empty` only when intentionally submitting a partially filled draft. Every `part` has exactly one string field: `text`, `binding_id`, `glyph_code`, or `asset_id`. Bindings must belong to the selected block or its recorded crops/slots; the server enforces the association. The exported JSON keeps original source text, transcriptions, figures/tables and verification status. JSON preserves codepoints exactly; it performs no Unicode normalization or traditional/simplified conversion. Markdown is a readable derivative and references the preserved image files.
|
|
118
|
+
|
|
119
|
+
Export saves assets referenced by the v1 API and the archived original document. It does not claim to enumerate every intermediate file kept inside the server. For large exports, consider `--no-images` first or a page-specific evidence packet.
|
|
120
|
+
|
|
121
|
+
## Agents and low-level access
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
guwenzi-tools agent guide --plain
|
|
125
|
+
guwenzi-tools agent install-skill --directory /your-agent/skills/guwenzi-remote
|
|
126
|
+
guwenzi-tools tools
|
|
127
|
+
guwenzi-tools tools --server
|
|
128
|
+
guwenzi-tools call glyph_search --json @request.json
|
|
129
|
+
guwenzi-tools call page_prepare --json @request.json --detach
|
|
130
|
+
guwenzi-tools status JOB_ID --wait
|
|
131
|
+
guwenzi-tools asset ASSET_ID --output original.png
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Convenience commands output one JSON envelope on stdout and progress on stderr. Full page/search responses are stored locally while summaries point the agent to image files; image base64 is not printed. `call` and `status` retain the v1 job envelope. Tasks already submitted continue on the server if the CLI stops waiting. Receipts retain job IDs without credentials. An uncertain POST outcome is not automatically repeated; GET failures and explicit429 rejections have bounded retries.
|
|
135
|
+
|
|
136
|
+
Any multimodal agent with shell access and an image-viewing function can use the same CLI. Hosted chat interfaces that cannot run commands need an application-side tool adapter; installing a Python package alone does not give a hosted model filesystem or shell access. No MCP connection or specific model provider is required.
|
|
137
|
+
|
|
138
|
+
## Python SDK
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from gwz_client import Client
|
|
142
|
+
from gwz_client.workflow import Preparation
|
|
143
|
+
|
|
144
|
+
with Client.from_config() as client:
|
|
145
|
+
print(client.health())
|
|
146
|
+
result = Preparation(client, "work/paper").run("paper.pdf", page_spec="1")
|
|
147
|
+
print(result["document_id"])
|
|
148
|
+
job = client.call("document_result", {"document_id": result["document_id"], "limit": 20})
|
|
149
|
+
print(job["result"])
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Credentials can also be supplied to `Client(endpoint, token, auth="raw")` by your application's secret store. The SDK and CLI share the same transport and integrity checks.
|
|
153
|
+
|
|
154
|
+
## Distribution and service access
|
|
155
|
+
|
|
156
|
+
PyPI distributes the client, not an inference entitlement. Each deployment owner provides an endpoint and access credentials separately. The current v1 service has one EAS access domain and a shared document store; profiles are client connection settings, not server-side tenant isolation. A public multi-user SaaS would need separate identity, quotas and data isolation on the service side.
|
|
157
|
+
|
|
158
|
+
This repository can build a wheel and source distribution with `python -m build`, and validate them with `python -m twine check dist/*`. Publication is a separate owner action. See `PUBLISHING.md` in the source distribution for TestPyPI/PyPI release steps. This MIT license covers the client only; server dependencies, model weights and external glyph datasets have their own licenses.
|
|
159
|
+
|
|
160
|
+
## Inline glyphs and real fonts
|
|
161
|
+
|
|
162
|
+
Page preparation and document export write `reading.html`: permanent gwz markers display as inline glyph images, with links to the enlarged originals. Long IDs remain in the evidence JSON. This view can be printed even when no usable font outline exists.
|
|
163
|
+
|
|
164
|
+
For a native PDF packet with preserved `.path` programs, install the optional font dependencies and export an ordinary OpenType CFF font:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
python -m pip install 'guwenzi-tools[fonts]'
|
|
168
|
+
guwenzi-tools font build --packet work/page-1 --output work/font
|
|
169
|
+
guwenzi-tools font encode text.txt --map work/font/glyph-map.json --output work/typeset.txt
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Install the generated `.otf` in your word processor, or explicitly embed it in your typesetting tool. `font-preview.html` embeds it for local browser viewing. `glyph-map.json` records its SHA256, font-local PUA characters, gwz identities, original path hashes and geometric transforms. Keep the font and mapping with the document. A gwz identifier alone is not an installed font character; PUA has meaning only with the matching font.
|
|
173
|
+
|
|
174
|
+
Supported original fill paths retain Bezier curves; independent fills are unioned before font encoding. Unsupported paint/clipping operations and conflicting outlines are reported, not silently traced. CFF has finite coordinate precision, and the export normalizes the glyph into a common em box; it does not recover the original font's complete metrics. Original evidence remains unchanged.
|
|
175
|
+
|
|
176
|
+
For a **printed bitmap** without usable original outlines, a multimodal agent can reuse the existing companion `guwenzi-glyph compose` CLI to write KAGE programs, render, inspect, revise and export fonts. This is explicitly a model reconstruction, not the original shape. Handwriting, rubbings and uncertain forms remain images. The companion CLI, Node, KAGE engine and GlyphWiki dump are separate from this lightweight client; `pip install guwenzi-tools` does not install them. The client itself does not infer a KAGE program from a bitmap.
|
|
177
|
+
|
|
178
|
+
`glyph search` deliberately shows images and request-scoped candidate IDs before revealing labels. `glyph select` returns the complete selected identity; `glyph resolve` saves it in `result.json` and includes an `identity` summary on stdout: source codepoints, font/source hashes, version, glyph ID/name and available source aliases/relations. `reading_status=no_confirmed_reading_in_v1` means the API does not provide a verified scholarly reading. A source font can borrow an unassigned, private-use or unrelated codepoint; neither a `U+...` label nor a glyph name confirms Unicode identity. `source_material_in_bundle=false` also means the metadata is available but the original font program is not in that deployed bundle.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "guwenzi-tools"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "CLI and Python client for evidence-preserving historical-document and glyph tools"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{name = "Guwenzi contributors"}]
|
|
14
|
+
keywords = ["ocr", "paleography", "glyph", "cli", "multimodal"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Image Recognition"
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["httpx>=0.28,<1", "filelock>=3.15,<4"]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
dev = ["build>=1.2,<2", "twine>=6,<7"]
|
|
26
|
+
fonts = ["fonttools>=4.60,<5", "skia-pathops>=0.8,<1"]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
guwenzi-tools = "gwz_client.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
gwz_client = ["data/*.json", "data/*.md", "data/skill/*.md"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Reject server/model/private artefacts before publication. Does not upload."""
|
|
2
|
+
import argparse
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import tarfile
|
|
8
|
+
import zipfile
|
|
9
|
+
|
|
10
|
+
FORBIDDEN_PATHS = ('.venv/','gwz_service/','datasets/','research/','training/','outputs/','.ossutilconfig','.pypirc')
|
|
11
|
+
FORBIDDEN_SUFFIXES = ('.pt','.pth','.npy','.sqlite3','.safetensors','.pdf','.png','.jpg','.jpeg')
|
|
12
|
+
PRIVATE_PATTERNS = (rb'/Users/[A-Za-z0-9._-]+/', rb'\d{12,20}\.cn-[a-z]+\.pai-eas',
|
|
13
|
+
rb'oss-pai-[a-z0-9]+-cn-[a-z]+', rb'crpi-[a-z0-9]+(?:-vpc)?\.cn-')
|
|
14
|
+
|
|
15
|
+
def audit(directory):
|
|
16
|
+
directory=Path(directory)
|
|
17
|
+
reports=[]
|
|
18
|
+
for file in sorted([*directory.glob('*.whl'),*directory.glob('*.tar.gz')]):
|
|
19
|
+
if file.name.endswith('.whl'):
|
|
20
|
+
with zipfile.ZipFile(file) as z:members=[(name,z.read(name)) for name in z.namelist() if not name.endswith('/')]
|
|
21
|
+
else:
|
|
22
|
+
with tarfile.open(file) as z:members=[(m.name,z.extractfile(m).read()) for m in z.getmembers() if m.isfile()]
|
|
23
|
+
for name,data in members:
|
|
24
|
+
if any(part in name for part in FORBIDDEN_PATHS) or name.endswith(FORBIDDEN_SUFFIXES):
|
|
25
|
+
raise ValueError('Forbidden package member: '+name)
|
|
26
|
+
if any(re.search(pattern,data) for pattern in PRIVATE_PATTERNS):
|
|
27
|
+
raise ValueError('Private deployment reference in package: '+name)
|
|
28
|
+
assert any(name.endswith('skill/SKILL.md') for name,_ in members),'Missing skill'
|
|
29
|
+
assert any(name.endswith('tools-v1.json') for name,_ in members),'Missing tool schema'
|
|
30
|
+
assert any(name.endswith('LICENSE') for name,_ in members),'Missing license'
|
|
31
|
+
reports.append({'file':file.name,'bytes':file.stat().st_size,'sha256':hashlib.sha256(file.read_bytes()).hexdigest(),
|
|
32
|
+
'members':len(members),'status':'passed'})
|
|
33
|
+
if len(reports)!=2:
|
|
34
|
+
raise ValueError('Expected one wheel and one sdist')
|
|
35
|
+
return {'status':'passed','artefacts':reports,'uploaded':False}
|
|
36
|
+
|
|
37
|
+
if __name__=='__main__':
|
|
38
|
+
p=argparse.ArgumentParser();p.add_argument('directory');a=p.parse_args()
|
|
39
|
+
print(json.dumps(audit(a.directory),indent=2))
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Exercise the installed client against a real v1 host; never logs tokens.
|
|
2
|
+
|
|
3
|
+
Provide a directory with native.pdf, raster.png and query.png. The fixtures
|
|
4
|
+
are not part of the distribution. This uploads them to the chosen service.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
def run(args):
|
|
15
|
+
root=Path(args.output).resolve();root.mkdir(parents=True,exist_ok=True)
|
|
16
|
+
env=dict(os.environ)
|
|
17
|
+
env['GWZ_CLIENT_CONFIG']=str(root/'profile.json')
|
|
18
|
+
# In real use configure a profile or supply GWZ_REMOTE_TOKEN externally.
|
|
19
|
+
commands=[]
|
|
20
|
+
def cli(*parts, label=None):
|
|
21
|
+
started=time.monotonic()
|
|
22
|
+
p=subprocess.run([sys.executable,'-m','gwz_client.cli',*map(str,parts)],env=env,capture_output=True,text=True)
|
|
23
|
+
try:response=json.loads(p.stdout)
|
|
24
|
+
except ValueError as exc:raise RuntimeError('Client did not return JSON: '+p.stderr[:2000]) from exc
|
|
25
|
+
if p.returncode or not response.get('ok'):
|
|
26
|
+
raise RuntimeError(json.dumps(response,ensure_ascii=True))
|
|
27
|
+
name=label or ('-'.join(str(x) for x in parts[:2]))
|
|
28
|
+
(root/(name+'.json')).write_text(json.dumps(response,ensure_ascii=True,indent=2))
|
|
29
|
+
commands.append({'command':name,'seconds':time.monotonic()-started,'ok':True})
|
|
30
|
+
print(json.dumps(commands[-1]),flush=True)
|
|
31
|
+
return response['result']
|
|
32
|
+
cli('configure','--endpoint',args.endpoint,'--token-env','GWZ_REMOTE_TOKEN',label='configure')
|
|
33
|
+
cli('doctor','--quick',label='doctor-quick')
|
|
34
|
+
cli('tools','--server',label='server-tools')
|
|
35
|
+
native=cli('document','prepare',Path(args.fixtures)/'native.pdf','--pages','3','--output',root/'native',label='prepare-native')
|
|
36
|
+
resume=cli('document','prepare',Path(args.fixtures)/'native.pdf','--pages','3','--output',root/'native','--resume',label='resume-native')
|
|
37
|
+
assert native['document_id']==resume['document_id']
|
|
38
|
+
raster=cli('document','prepare',Path(args.fixtures)/'raster.png','--pages','1','--output',root/'raster',label='prepare-raster')
|
|
39
|
+
page=json.loads((root/'raster/pages/p0001/result.json').read_text())
|
|
40
|
+
assert any(b['type']=='table' for b in page['blocks'])
|
|
41
|
+
line=next(b for b in page['blocks'] if b['type']=='text' and b.get('slot_count',0)>1)
|
|
42
|
+
split=cli('line','split',line['asset']['asset_id'],'--output',root/'split',label='split-line')
|
|
43
|
+
slot=split['slots'][0]
|
|
44
|
+
crop=cli('crop',line['asset']['asset_id'],'--box',*slot['bbox_px'],'--output',root/'crop',label='crop-slot')
|
|
45
|
+
assert crop['asset_id']==slot['asset_id']
|
|
46
|
+
unknown=cli('glyph','register',crop['asset_id'],label='register-original')
|
|
47
|
+
transcription={'document_id':raster['document_id'],'page':1,'block_id':line['block_id'],
|
|
48
|
+
'parts':[{'text':'整合測試𠀀後:'},{'glyph_code':unknown['code']}]}
|
|
49
|
+
request=root/'transcribe.json';request.write_text(json.dumps(transcription,ensure_ascii=True))
|
|
50
|
+
cli('transcribe',request,label='transcribe-original')
|
|
51
|
+
uploaded=cli('upload',Path(args.fixtures)/'query.png',label='upload-query')
|
|
52
|
+
imported=subprocess.run([sys.executable,'-m','gwz_client.cli','call','asset_import','--json',
|
|
53
|
+
json.dumps({'input_key':uploaded['input_key']})],env=env,capture_output=True,text=True)
|
|
54
|
+
envelope=json.loads(imported.stdout)
|
|
55
|
+
assert imported.returncode==0 and envelope['ok']
|
|
56
|
+
aid=envelope['result']['asset_id']
|
|
57
|
+
search=cli('glyph','search',aid,'--output',root/'search',label='search-eight')
|
|
58
|
+
assert len(search['candidates'])==8
|
|
59
|
+
for c in search['candidates']:
|
|
60
|
+
assert Path(c['image']).is_file() and Path(c['view']).is_file()
|
|
61
|
+
# This is a scoped-binding regression check, not an autonomous visual
|
|
62
|
+
# model or an accuracy benchmark. Choice is explicit in the test.
|
|
63
|
+
selected=cli('glyph','select',search['search_id'],'c1',label='select-known-regression-candidate')
|
|
64
|
+
assert selected['recognition_status']=='model_selected_unverified'
|
|
65
|
+
cli('glyph','resolve',selected['code'],'--output',root/'resolved',label='resolve-code')
|
|
66
|
+
exported=cli('document','export',raster['document_id'],'--output',root/'export',label='export-document')
|
|
67
|
+
assert '整合測試𠀀後' in Path(exported['markdown']).read_text()
|
|
68
|
+
assert not exported['content_verified']
|
|
69
|
+
report={'status':'passed','commands':commands,'client_version':'0.2.0','protocol':'guwenzi.tools.v1',
|
|
70
|
+
'native_document_id':native['document_id'],'raster_document_id':raster['document_id'],
|
|
71
|
+
'selected_code':selected['code'],'raster_blocks':len(page['blocks']),
|
|
72
|
+
'note':'Real transport/model tests; explicit regression selection and synthetic transcription, not OCR accuracy.'}
|
|
73
|
+
(root/'report.json').write_text(json.dumps(report,indent=2))
|
|
74
|
+
print(json.dumps({'status':'passed','report':str(root/'report.json')}))
|
|
75
|
+
|
|
76
|
+
if __name__=='__main__':
|
|
77
|
+
p=argparse.ArgumentParser();p.add_argument('--endpoint',required=True);p.add_argument('--fixtures',required=True);p.add_argument('--output',required=True)
|
|
78
|
+
run(p.parse_args())
|