wenyi-core 1.4.2__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.
Files changed (365) hide show
  1. wenyi_core-1.4.2/.gitignore +3 -0
  2. wenyi_core-1.4.2/PKG-INFO +40 -0
  3. wenyi_core-1.4.2/README.md +18 -0
  4. wenyi_core-1.4.2/pyproject.toml +38 -0
  5. wenyi_core-1.4.2/tests/__init__.py +0 -0
  6. wenyi_core-1.4.2/tests/conftest.py +31 -0
  7. wenyi_core-1.4.2/tests/fake_llm.py +117 -0
  8. wenyi_core-1.4.2/tests/model_fixtures.py +26 -0
  9. wenyi_core-1.4.2/tests/pipeline_fixtures.py +55 -0
  10. wenyi_core-1.4.2/tests/precision_fixtures.py +106 -0
  11. wenyi_core-1.4.2/tests/review_fixtures.py +130 -0
  12. wenyi_core-1.4.2/tests/sample_data.py +450 -0
  13. wenyi_core-1.4.2/tests/storage_fixtures.py +61 -0
  14. wenyi_core-1.4.2/tests/terminology_fixtures.py +180 -0
  15. wenyi_core-1.4.2/tests/test_annotation_aligner.py +445 -0
  16. wenyi_core-1.4.2/tests/test_annotations.py +138 -0
  17. wenyi_core-1.4.2/tests/test_architecture_boundaries.py +748 -0
  18. wenyi_core-1.4.2/tests/test_assemble.py +1765 -0
  19. wenyi_core-1.4.2/tests/test_atomicio.py +87 -0
  20. wenyi_core-1.4.2/tests/test_babeldoc_bridge_client.py +248 -0
  21. wenyi_core-1.4.2/tests/test_babeldoc_context.py +487 -0
  22. wenyi_core-1.4.2/tests/test_babeldoc_fillback.py +50 -0
  23. wenyi_core-1.4.2/tests/test_bilingual.py +569 -0
  24. wenyi_core-1.4.2/tests/test_book_understanding.py +431 -0
  25. wenyi_core-1.4.2/tests/test_cache_accounting.py +266 -0
  26. wenyi_core-1.4.2/tests/test_ci_gates.py +284 -0
  27. wenyi_core-1.4.2/tests/test_config.py +217 -0
  28. wenyi_core-1.4.2/tests/test_desktop_updates.py +216 -0
  29. wenyi_core-1.4.2/tests/test_docx.py +661 -0
  30. wenyi_core-1.4.2/tests/test_docx_bilingual.py +183 -0
  31. wenyi_core-1.4.2/tests/test_errors.py +179 -0
  32. wenyi_core-1.4.2/tests/test_export_guards.py +320 -0
  33. wenyi_core-1.4.2/tests/test_file_artifacts.py +322 -0
  34. wenyi_core-1.4.2/tests/test_file_migrations.py +102 -0
  35. wenyi_core-1.4.2/tests/test_file_storage.py +463 -0
  36. wenyi_core-1.4.2/tests/test_glossary.py +227 -0
  37. wenyi_core-1.4.2/tests/test_glossary_agents.py +413 -0
  38. wenyi_core-1.4.2/tests/test_glossary_anki.py +206 -0
  39. wenyi_core-1.4.2/tests/test_glossary_history_cache.py +272 -0
  40. wenyi_core-1.4.2/tests/test_glossary_pagination.py +51 -0
  41. wenyi_core-1.4.2/tests/test_glossary_readings.py +194 -0
  42. wenyi_core-1.4.2/tests/test_glossary_snapshot_checkpoint_race.py +62 -0
  43. wenyi_core-1.4.2/tests/test_i18n.py +401 -0
  44. wenyi_core-1.4.2/tests/test_ingest.py +1654 -0
  45. wenyi_core-1.4.2/tests/test_ingest_encoding.py +79 -0
  46. wenyi_core-1.4.2/tests/test_input_preparation.py +306 -0
  47. wenyi_core-1.4.2/tests/test_language_detection_errors.py +105 -0
  48. wenyi_core-1.4.2/tests/test_language_policy.py +254 -0
  49. wenyi_core-1.4.2/tests/test_language_policy_resume.py +185 -0
  50. wenyi_core-1.4.2/tests/test_language_policy_text.py +190 -0
  51. wenyi_core-1.4.2/tests/test_llm.py +502 -0
  52. wenyi_core-1.4.2/tests/test_llm_capture.py +181 -0
  53. wenyi_core-1.4.2/tests/test_llm_connectivity.py +233 -0
  54. wenyi_core-1.4.2/tests/test_llm_gemini.py +340 -0
  55. wenyi_core-1.4.2/tests/test_llm_models.py +144 -0
  56. wenyi_core-1.4.2/tests/test_llm_retrying.py +328 -0
  57. wenyi_core-1.4.2/tests/test_llm_routing.py +657 -0
  58. wenyi_core-1.4.2/tests/test_llm_safe_errors.py +217 -0
  59. wenyi_core-1.4.2/tests/test_locks_contract.py +427 -0
  60. wenyi_core-1.4.2/tests/test_metadata_language.py +131 -0
  61. wenyi_core-1.4.2/tests/test_mineru_credentials.py +218 -0
  62. wenyi_core-1.4.2/tests/test_newfeatures.py +345 -0
  63. wenyi_core-1.4.2/tests/test_nginx_proxy.py +177 -0
  64. wenyi_core-1.4.2/tests/test_observability.py +117 -0
  65. wenyi_core-1.4.2/tests/test_opencode_go.py +64 -0
  66. wenyi_core-1.4.2/tests/test_orchestrator.py +2779 -0
  67. wenyi_core-1.4.2/tests/test_orchestrator_contract.py +431 -0
  68. wenyi_core-1.4.2/tests/test_pdf_export_defaults.py +288 -0
  69. wenyi_core-1.4.2/tests/test_pdf_support.py +606 -0
  70. wenyi_core-1.4.2/tests/test_pdf_to_html_client.py +85 -0
  71. wenyi_core-1.4.2/tests/test_precision_agent.py +297 -0
  72. wenyi_core-1.4.2/tests/test_precision_archive.py +351 -0
  73. wenyi_core-1.4.2/tests/test_precision_configuration.py +151 -0
  74. wenyi_core-1.4.2/tests/test_precision_drafts.py +208 -0
  75. wenyi_core-1.4.2/tests/test_precision_pipeline.py +355 -0
  76. wenyi_core-1.4.2/tests/test_precision_records.py +363 -0
  77. wenyi_core-1.4.2/tests/test_preparation.py +305 -0
  78. wenyi_core-1.4.2/tests/test_preparation_modes.py +158 -0
  79. wenyi_core-1.4.2/tests/test_prompt_evidence.py +205 -0
  80. wenyi_core-1.4.2/tests/test_provider_errors.py +82 -0
  81. wenyi_core-1.4.2/tests/test_release_packaging.py +533 -0
  82. wenyi_core-1.4.2/tests/test_review_actions.py +262 -0
  83. wenyi_core-1.4.2/tests/test_review_agent.py +1538 -0
  84. wenyi_core-1.4.2/tests/test_review_arbiter_prompt.py +190 -0
  85. wenyi_core-1.4.2/tests/test_review_autofix.py +597 -0
  86. wenyi_core-1.4.2/tests/test_review_checkpoint.py +147 -0
  87. wenyi_core-1.4.2/tests/test_review_evidence.py +255 -0
  88. wenyi_core-1.4.2/tests/test_review_fixer.py +190 -0
  89. wenyi_core-1.4.2/tests/test_review_polish.py +545 -0
  90. wenyi_core-1.4.2/tests/test_review_run_store.py +263 -0
  91. wenyi_core-1.4.2/tests/test_review_session.py +235 -0
  92. wenyi_core-1.4.2/tests/test_rolling_context.py +63 -0
  93. wenyi_core-1.4.2/tests/test_routing_resume.py +396 -0
  94. wenyi_core-1.4.2/tests/test_run_report.py +172 -0
  95. wenyi_core-1.4.2/tests/test_runtime_usage.py +216 -0
  96. wenyi_core-1.4.2/tests/test_sqlite_storage.py +919 -0
  97. wenyi_core-1.4.2/tests/test_storage_injection.py +165 -0
  98. wenyi_core-1.4.2/tests/test_terminology_agent.py +232 -0
  99. wenyi_core-1.4.2/tests/test_terminology_citations.py +186 -0
  100. wenyi_core-1.4.2/tests/test_terminology_discovery.py +236 -0
  101. wenyi_core-1.4.2/tests/test_terminology_entrypoint.py +214 -0
  102. wenyi_core-1.4.2/tests/test_terminology_evidence.py +67 -0
  103. wenyi_core-1.4.2/tests/test_terminology_person_scope.py +203 -0
  104. wenyi_core-1.4.2/tests/test_terminology_pipeline.py +430 -0
  105. wenyi_core-1.4.2/tests/test_terminology_policy.py +159 -0
  106. wenyi_core-1.4.2/tests/test_terminology_service.py +326 -0
  107. wenyi_core-1.4.2/tests/test_terminology_transport.py +121 -0
  108. wenyi_core-1.4.2/tests/test_terminology_validation.py +159 -0
  109. wenyi_core-1.4.2/tests/test_test_layout.py +75 -0
  110. wenyi_core-1.4.2/tests/test_timing.py +210 -0
  111. wenyi_core-1.4.2/tests/test_title_translation.py +184 -0
  112. wenyi_core-1.4.2/tests/test_translation_batch.py +253 -0
  113. wenyi_core-1.4.2/tests/test_translation_context.py +575 -0
  114. wenyi_core-1.4.2/tests/test_translator.py +336 -0
  115. wenyi_core-1.4.2/tests/test_usage.py +1028 -0
  116. wenyi_core-1.4.2/wenyi_core/__init__.py +1 -0
  117. wenyi_core-1.4.2/wenyi_core/agents/__init__.py +1 -0
  118. wenyi_core-1.4.2/wenyi_core/agents/analyzer.py +109 -0
  119. wenyi_core-1.4.2/wenyi_core/agents/annotation_aligner.py +521 -0
  120. wenyi_core-1.4.2/wenyi_core/agents/base.py +223 -0
  121. wenyi_core-1.4.2/wenyi_core/agents/glossary_extractor.py +358 -0
  122. wenyi_core-1.4.2/wenyi_core/agents/polisher.py +98 -0
  123. wenyi_core-1.4.2/wenyi_core/agents/precision.py +186 -0
  124. wenyi_core-1.4.2/wenyi_core/agents/prompts.py +105 -0
  125. wenyi_core-1.4.2/wenyi_core/agents/review_actions.py +389 -0
  126. wenyi_core-1.4.2/wenyi_core/agents/review_arbiter.py +211 -0
  127. wenyi_core-1.4.2/wenyi_core/agents/review_fixer.py +354 -0
  128. wenyi_core-1.4.2/wenyi_core/agents/review_loop.py +268 -0
  129. wenyi_core-1.4.2/wenyi_core/agents/reviewer.py +173 -0
  130. wenyi_core-1.4.2/wenyi_core/agents/synopsis.py +157 -0
  131. wenyi_core-1.4.2/wenyi_core/agents/terminology.py +429 -0
  132. wenyi_core-1.4.2/wenyi_core/agents/title_translator.py +41 -0
  133. wenyi_core-1.4.2/wenyi_core/agents/translator.py +281 -0
  134. wenyi_core-1.4.2/wenyi_core/assemble/__init__.py +1 -0
  135. wenyi_core-1.4.2/wenyi_core/assemble/about.py +136 -0
  136. wenyi_core-1.4.2/wenyi_core/assemble/docx_blocks.py +354 -0
  137. wenyi_core-1.4.2/wenyi_core/assemble/docx_numbering.py +83 -0
  138. wenyi_core-1.4.2/wenyi_core/assemble/docx_styles.py +250 -0
  139. wenyi_core-1.4.2/wenyi_core/assemble/docx_writer.py +69 -0
  140. wenyi_core-1.4.2/wenyi_core/assemble/epub_navigation.py +199 -0
  141. wenyi_core-1.4.2/wenyi_core/assemble/epub_presentation.py +152 -0
  142. wenyi_core-1.4.2/wenyi_core/assemble/epub_resources.py +197 -0
  143. wenyi_core-1.4.2/wenyi_core/assemble/epub_writer.py +388 -0
  144. wenyi_core-1.4.2/wenyi_core/assemble/export_view.py +156 -0
  145. wenyi_core-1.4.2/wenyi_core/assemble/html_bilingual.py +214 -0
  146. wenyi_core-1.4.2/wenyi_core/assemble/html_inline.py +344 -0
  147. wenyi_core-1.4.2/wenyi_core/assemble/html_renderer.py +237 -0
  148. wenyi_core-1.4.2/wenyi_core/assemble/html_resources.py +230 -0
  149. wenyi_core-1.4.2/wenyi_core/assemble/html_writer.py +175 -0
  150. wenyi_core-1.4.2/wenyi_core/assemble/pdf_writer.py +18 -0
  151. wenyi_core-1.4.2/wenyi_core/assemble/policy.py +93 -0
  152. wenyi_core-1.4.2/wenyi_core/assemble/text_writer.py +63 -0
  153. wenyi_core-1.4.2/wenyi_core/assemble/writer.py +179 -0
  154. wenyi_core-1.4.2/wenyi_core/assemble/writer_common.py +185 -0
  155. wenyi_core-1.4.2/wenyi_core/atomicio.py +82 -0
  156. wenyi_core-1.4.2/wenyi_core/config.py +405 -0
  157. wenyi_core-1.4.2/wenyi_core/document_styles/__init__.py +1 -0
  158. wenyi_core-1.4.2/wenyi_core/document_styles/docx.py +147 -0
  159. wenyi_core-1.4.2/wenyi_core/errors.py +59 -0
  160. wenyi_core-1.4.2/wenyi_core/events.py +67 -0
  161. wenyi_core-1.4.2/wenyi_core/glossary/__init__.py +5 -0
  162. wenyi_core-1.4.2/wenyi_core/glossary/anki.py +317 -0
  163. wenyi_core-1.4.2/wenyi_core/glossary/evidence.py +111 -0
  164. wenyi_core-1.4.2/wenyi_core/glossary/evidence_models.py +46 -0
  165. wenyi_core-1.4.2/wenyi_core/glossary/resolver.py +18 -0
  166. wenyi_core-1.4.2/wenyi_core/glossary/store.py +487 -0
  167. wenyi_core-1.4.2/wenyi_core/i18n/__init__.py +5 -0
  168. wenyi_core-1.4.2/wenyi_core/i18n/data/export/about.en.xhtml +18 -0
  169. wenyi_core-1.4.2/wenyi_core/i18n/data/export/about.zh.xhtml +39 -0
  170. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/de.json +10 -0
  171. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/en-GB.json +6 -0
  172. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/en-US.json +6 -0
  173. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/en.json +10 -0
  174. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/es.json +10 -0
  175. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/fr.json +10 -0
  176. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/it.json +10 -0
  177. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/ja.json +19 -0
  178. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/ko.json +10 -0
  179. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/pt-BR.json +6 -0
  180. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/pt-PT.json +6 -0
  181. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/pt.json +10 -0
  182. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/registry.json +97 -0
  183. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/ru.json +10 -0
  184. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/vi.json +10 -0
  185. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/zh-Hant.json +19 -0
  186. wenyi_core-1.4.2/wenyi_core/i18n/data/languages/zh.json +24 -0
  187. wenyi_core-1.4.2/wenyi_core/i18n/data/pairs/ja__zh.json +5 -0
  188. wenyi_core-1.4.2/wenyi_core/i18n/data/pairs/registry.json +3 -0
  189. wenyi_core-1.4.2/wenyi_core/i18n/data/shared/guidance.json +4 -0
  190. wenyi_core-1.4.2/wenyi_core/i18n/data/shared/honorific.json +5 -0
  191. wenyi_core-1.4.2/wenyi_core/i18n/data/shared/metadata_guidance.txt +4 -0
  192. wenyi_core-1.4.2/wenyi_core/i18n/data/shared/review_evidence_tools.txt +14 -0
  193. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/analyzer_system.txt +17 -0
  194. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/analyzer_user.txt +5 -0
  195. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/book_synopsis_system.txt +3 -0
  196. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/book_synopsis_user.txt +7 -0
  197. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/chapter_digest_system.txt +2 -0
  198. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/chapter_digest_user.txt +4 -0
  199. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_extractor_system.txt +16 -0
  200. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_extractor_user.txt +10 -0
  201. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_history_system.txt +6 -0
  202. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_history_user.txt +4 -0
  203. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/language_detector_system.txt +3 -0
  204. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_continue_user.txt +10 -0
  205. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_system.txt +6 -0
  206. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_terminology_user.txt +15 -0
  207. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_user.txt +13 -0
  208. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_generation_system.txt +9 -0
  209. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_packet.txt +2 -0
  210. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_synthesize.txt +5 -0
  211. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_translate.txt +3 -0
  212. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_agent_system.txt +32 -0
  213. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_agent_user.txt +12 -0
  214. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_arbiter_system.txt +21 -0
  215. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_arbiter_user.txt +4 -0
  216. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_fixer_system.txt +12 -0
  217. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_fixer_user.txt +30 -0
  218. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/reviewer_system.txt +11 -0
  219. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/reviewer_user.txt +7 -0
  220. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_discover_system.txt +4 -0
  221. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_discover_user.txt +2 -0
  222. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_evidence_system.txt +5 -0
  223. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_evidence_user.txt +2 -0
  224. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_merge_system.txt +4 -0
  225. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_merge_user.txt +2 -0
  226. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/title_translator_system.txt +7 -0
  227. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/title_translator_user.txt +7 -0
  228. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/translator_system.txt +11 -0
  229. wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/translator_user.txt +25 -0
  230. wenyi_core-1.4.2/wenyi_core/i18n/languages.py +96 -0
  231. wenyi_core-1.4.2/wenyi_core/i18n/metadata.py +13 -0
  232. wenyi_core-1.4.2/wenyi_core/i18n/policy/__init__.py +6 -0
  233. wenyi_core-1.4.2/wenyi_core/i18n/policy/models.py +225 -0
  234. wenyi_core-1.4.2/wenyi_core/i18n/policy/registry.py +99 -0
  235. wenyi_core-1.4.2/wenyi_core/i18n/policy/resolver.py +381 -0
  236. wenyi_core-1.4.2/wenyi_core/i18n/policy/tasks.py +40 -0
  237. wenyi_core-1.4.2/wenyi_core/i18n/prompts.py +48 -0
  238. wenyi_core-1.4.2/wenyi_core/i18n/resources.py +28 -0
  239. wenyi_core-1.4.2/wenyi_core/ingest/__init__.py +1 -0
  240. wenyi_core-1.4.2/wenyi_core/ingest/docx_reader.py +562 -0
  241. wenyi_core-1.4.2/wenyi_core/ingest/encoding.py +84 -0
  242. wenyi_core-1.4.2/wenyi_core/ingest/epub_chapters.py +67 -0
  243. wenyi_core-1.4.2/wenyi_core/ingest/epub_layout.py +360 -0
  244. wenyi_core-1.4.2/wenyi_core/ingest/epub_package.py +126 -0
  245. wenyi_core-1.4.2/wenyi_core/ingest/epub_reader.py +120 -0
  246. wenyi_core-1.4.2/wenyi_core/ingest/epub_toc.py +205 -0
  247. wenyi_core-1.4.2/wenyi_core/ingest/errors.py +15 -0
  248. wenyi_core-1.4.2/wenyi_core/ingest/fb2_reader.py +319 -0
  249. wenyi_core-1.4.2/wenyi_core/ingest/html_reader.py +182 -0
  250. wenyi_core-1.4.2/wenyi_core/ingest/models.py +17 -0
  251. wenyi_core-1.4.2/wenyi_core/ingest/pdf_babeldoc.py +368 -0
  252. wenyi_core-1.4.2/wenyi_core/ingest/pdf_reader.py +124 -0
  253. wenyi_core-1.4.2/wenyi_core/ingest/pdf_to_html.py +365 -0
  254. wenyi_core-1.4.2/wenyi_core/ingest/segmenter.py +216 -0
  255. wenyi_core-1.4.2/wenyi_core/ingest/source_hash.py +14 -0
  256. wenyi_core-1.4.2/wenyi_core/ingest/text_reader.py +98 -0
  257. wenyi_core-1.4.2/wenyi_core/ingest/tokens.py +31 -0
  258. wenyi_core-1.4.2/wenyi_core/llm/__init__.py +1 -0
  259. wenyi_core-1.4.2/wenyi_core/llm/base.py +147 -0
  260. wenyi_core-1.4.2/wenyi_core/llm/configuration.py +171 -0
  261. wenyi_core-1.4.2/wenyi_core/llm/connectivity.py +215 -0
  262. wenyi_core-1.4.2/wenyi_core/llm/credentials.py +22 -0
  263. wenyi_core-1.4.2/wenyi_core/llm/errors.py +93 -0
  264. wenyi_core-1.4.2/wenyi_core/llm/factory.py +14 -0
  265. wenyi_core-1.4.2/wenyi_core/llm/json_parser.py +120 -0
  266. wenyi_core-1.4.2/wenyi_core/llm/limits.py +135 -0
  267. wenyi_core-1.4.2/wenyi_core/llm/migration.py +84 -0
  268. wenyi_core-1.4.2/wenyi_core/llm/models.py +82 -0
  269. wenyi_core-1.4.2/wenyi_core/llm/operation_names.py +48 -0
  270. wenyi_core-1.4.2/wenyi_core/llm/operations.py +209 -0
  271. wenyi_core-1.4.2/wenyi_core/llm/providers/__init__.py +1 -0
  272. wenyi_core-1.4.2/wenyi_core/llm/providers/_openai_compatible.py +207 -0
  273. wenyi_core-1.4.2/wenyi_core/llm/providers/deepseek.py +108 -0
  274. wenyi_core-1.4.2/wenyi_core/llm/providers/fake.py +91 -0
  275. wenyi_core-1.4.2/wenyi_core/llm/providers/gemini.py +287 -0
  276. wenyi_core-1.4.2/wenyi_core/llm/providers/ollama.py +9 -0
  277. wenyi_core-1.4.2/wenyi_core/llm/providers/openai.py +73 -0
  278. wenyi_core-1.4.2/wenyi_core/llm/providers/openai_compatible.py +113 -0
  279. wenyi_core-1.4.2/wenyi_core/llm/providers/opencode_go.py +102 -0
  280. wenyi_core-1.4.2/wenyi_core/llm/providers/openrouter.py +73 -0
  281. wenyi_core-1.4.2/wenyi_core/llm/providers/orcarouter.py +12 -0
  282. wenyi_core-1.4.2/wenyi_core/llm/providers/vllm.py +9 -0
  283. wenyi_core-1.4.2/wenyi_core/llm/registry.py +146 -0
  284. wenyi_core-1.4.2/wenyi_core/llm/retrying.py +418 -0
  285. wenyi_core-1.4.2/wenyi_core/llm/router.py +286 -0
  286. wenyi_core-1.4.2/wenyi_core/llm/routing.py +159 -0
  287. wenyi_core-1.4.2/wenyi_core/llm/transport.py +121 -0
  288. wenyi_core-1.4.2/wenyi_core/llm/usage.py +325 -0
  289. wenyi_core-1.4.2/wenyi_core/markup/__init__.py +1 -0
  290. wenyi_core-1.4.2/wenyi_core/markup/anchors.py +108 -0
  291. wenyi_core-1.4.2/wenyi_core/markup/annotations.py +454 -0
  292. wenyi_core-1.4.2/wenyi_core/markup/contracts.py +56 -0
  293. wenyi_core-1.4.2/wenyi_core/markup/ruby.py +20 -0
  294. wenyi_core-1.4.2/wenyi_core/markup/segments.py +488 -0
  295. wenyi_core-1.4.2/wenyi_core/models/__init__.py +38 -0
  296. wenyi_core-1.4.2/wenyi_core/models/document.py +60 -0
  297. wenyi_core-1.4.2/wenyi_core/models/errors.py +26 -0
  298. wenyi_core-1.4.2/wenyi_core/models/glossary.py +80 -0
  299. wenyi_core-1.4.2/wenyi_core/observability.py +119 -0
  300. wenyi_core-1.4.2/wenyi_core/pdf_bridge/__init__.py +8 -0
  301. wenyi_core-1.4.2/wenyi_core/pdf_bridge/client.py +102 -0
  302. wenyi_core-1.4.2/wenyi_core/pdf_bridge/connection.py +35 -0
  303. wenyi_core-1.4.2/wenyi_core/pipeline/__init__.py +111 -0
  304. wenyi_core-1.4.2/wenyi_core/pipeline/annotate/__init__.py +1 -0
  305. wenyi_core-1.4.2/wenyi_core/pipeline/annotate/annotations.py +285 -0
  306. wenyi_core-1.4.2/wenyi_core/pipeline/annotate/docx_styles.py +153 -0
  307. wenyi_core-1.4.2/wenyi_core/pipeline/autofix/__init__.py +1 -0
  308. wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_candidates.py +269 -0
  309. wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_plan.py +77 -0
  310. wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_publish.py +198 -0
  311. wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_verification.py +276 -0
  312. wenyi_core-1.4.2/wenyi_core/pipeline/autofix/review_autofix.py +170 -0
  313. wenyi_core-1.4.2/wenyi_core/pipeline/finalize/__init__.py +1 -0
  314. wenyi_core-1.4.2/wenyi_core/pipeline/finalize/finalization.py +164 -0
  315. wenyi_core-1.4.2/wenyi_core/pipeline/orchestrator.py +361 -0
  316. wenyi_core-1.4.2/wenyi_core/pipeline/prepare/__init__.py +1 -0
  317. wenyi_core-1.4.2/wenyi_core/pipeline/prepare/book_understanding.py +235 -0
  318. wenyi_core-1.4.2/wenyi_core/pipeline/prepare/input_preparation.py +138 -0
  319. wenyi_core-1.4.2/wenyi_core/pipeline/prepare/preparation.py +397 -0
  320. wenyi_core-1.4.2/wenyi_core/pipeline/review/__init__.py +1 -0
  321. wenyi_core-1.4.2/wenyi_core/pipeline/review/review_checkpoint.py +169 -0
  322. wenyi_core-1.4.2/wenyi_core/pipeline/review/review_chunks.py +500 -0
  323. wenyi_core-1.4.2/wenyi_core/pipeline/review/review_results.py +314 -0
  324. wenyi_core-1.4.2/wenyi_core/pipeline/review/review_rounds.py +429 -0
  325. wenyi_core-1.4.2/wenyi_core/pipeline/review/review_workflow.py +420 -0
  326. wenyi_core-1.4.2/wenyi_core/pipeline/run/__init__.py +1 -0
  327. wenyi_core-1.4.2/wenyi_core/pipeline/run/language_policies.py +52 -0
  328. wenyi_core-1.4.2/wenyi_core/pipeline/run/runtime.py +178 -0
  329. wenyi_core-1.4.2/wenyi_core/pipeline/run/runtime_usage.py +81 -0
  330. wenyi_core-1.4.2/wenyi_core/pipeline/translate/__init__.py +1 -0
  331. wenyi_core-1.4.2/wenyi_core/pipeline/translate/babeldoc_context.py +136 -0
  332. wenyi_core-1.4.2/wenyi_core/pipeline/translate/context.py +44 -0
  333. wenyi_core-1.4.2/wenyi_core/pipeline/translate/precision.py +427 -0
  334. wenyi_core-1.4.2/wenyi_core/pipeline/translate/precision_records.py +198 -0
  335. wenyi_core-1.4.2/wenyi_core/pipeline/translate/terminology.py +364 -0
  336. wenyi_core-1.4.2/wenyi_core/pipeline/translate/terminology_metadata.py +27 -0
  337. wenyi_core-1.4.2/wenyi_core/pipeline/translate/title_translation.py +239 -0
  338. wenyi_core-1.4.2/wenyi_core/pipeline/translate/translation.py +637 -0
  339. wenyi_core-1.4.2/wenyi_core/pipeline/translate/translation_batch.py +142 -0
  340. wenyi_core-1.4.2/wenyi_core/postprocess/__init__.py +0 -0
  341. wenyi_core-1.4.2/wenyi_core/postprocess/export_text.py +92 -0
  342. wenyi_core-1.4.2/wenyi_core/postprocess/punct.py +155 -0
  343. wenyi_core-1.4.2/wenyi_core/review/__init__.py +5 -0
  344. wenyi_core-1.4.2/wenyi_core/review/autofix_models.py +111 -0
  345. wenyi_core-1.4.2/wenyi_core/review/conflicts.py +173 -0
  346. wenyi_core-1.4.2/wenyi_core/review/contracts.py +36 -0
  347. wenyi_core-1.4.2/wenyi_core/review/evidence.py +361 -0
  348. wenyi_core-1.4.2/wenyi_core/review/models.py +144 -0
  349. wenyi_core-1.4.2/wenyi_core/review/run_store.py +463 -0
  350. wenyi_core-1.4.2/wenyi_core/review/session.py +396 -0
  351. wenyi_core-1.4.2/wenyi_core/storage/__init__.py +20 -0
  352. wenyi_core-1.4.2/wenyi_core/storage/artifacts.py +86 -0
  353. wenyi_core-1.4.2/wenyi_core/storage/atomic_write.py +134 -0
  354. wenyi_core-1.4.2/wenyi_core/storage/file.py +254 -0
  355. wenyi_core-1.4.2/wenyi_core/storage/file_migrations.py +38 -0
  356. wenyi_core-1.4.2/wenyi_core/storage/io_path.py +68 -0
  357. wenyi_core-1.4.2/wenyi_core/storage/language_policies.py +69 -0
  358. wenyi_core-1.4.2/wenyi_core/storage/locks.py +350 -0
  359. wenyi_core-1.4.2/wenyi_core/storage/names.py +49 -0
  360. wenyi_core-1.4.2/wenyi_core/storage/precision_archive.py +605 -0
  361. wenyi_core-1.4.2/wenyi_core/storage/precision_drafts.py +203 -0
  362. wenyi_core-1.4.2/wenyi_core/storage/protocol.py +98 -0
  363. wenyi_core-1.4.2/wenyi_core/storage/run_store.py +606 -0
  364. wenyi_core-1.4.2/wenyi_core/storage/sqlite.py +1035 -0
  365. wenyi_core-1.4.2/wenyi_core/timing.py +137 -0
@@ -0,0 +1,3 @@
1
+ # Bundled language and prompt resources are source files, including .txt templates.
2
+ !wenyi_core/i18n/data/
3
+ !wenyi_core/i18n/data/**
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.5
2
+ Name: wenyi-core
3
+ Version: 1.4.2
4
+ Summary: Shared translation engine for multilingual long-form documents.
5
+ Project-URL: Repository, https://github.com/BigDawnGhost/wenyi
6
+ Project-URL: Documentation, https://github.com/BigDawnGhost/wenyi/blob/main/docs/architecture.md
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: beautifulsoup4>=4.12
9
+ Requires-Dist: ebooklib>=0.18
10
+ Requires-Dist: google-genai>=2.13.0
11
+ Requires-Dist: httpx>=0.27
12
+ Requires-Dist: json-repair<1,>=0.61
13
+ Requires-Dist: lxml>=5.0
14
+ Requires-Dist: openai>=1.40
15
+ Requires-Dist: pydantic>=2.7
16
+ Requires-Dist: pypdf>=6.14.2
17
+ Requires-Dist: python-docx>=1.2.0
18
+ Requires-Dist: pyyaml>=6.0
19
+ Requires-Dist: tenacity>=8.2
20
+ Requires-Dist: tiktoken>=0.14.0
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Wenyi Core
24
+
25
+ Shared translation engine for multilingual long-form documents. It provides
26
+ document ingestion, terminology management, contextual translation, polishing,
27
+ whole-book review, resumable state, and export services.
28
+
29
+ For the local command-line application, install **`wenyi-cli`** instead; it
30
+ installs this package automatically. Core does not install the Web API or Desktop
31
+ application.
32
+
33
+ PDF layout preservation uses an optional, independently installed
34
+ `wenyi-babeldoc-bridge` service over HTTP. BabelDOC is not a dependency of this
35
+ package.
36
+
37
+ - [Source and license](https://github.com/BigDawnGhost/wenyi)
38
+ - [Architecture](https://github.com/BigDawnGhost/wenyi/blob/main/docs/architecture.md)
39
+ - [CLI guide](https://github.com/BigDawnGhost/wenyi/blob/main/docs/cli.md)
40
+ - [简体中文](https://github.com/BigDawnGhost/wenyi/blob/main/docs/zh/README.md)
@@ -0,0 +1,18 @@
1
+ # Wenyi Core
2
+
3
+ Shared translation engine for multilingual long-form documents. It provides
4
+ document ingestion, terminology management, contextual translation, polishing,
5
+ whole-book review, resumable state, and export services.
6
+
7
+ For the local command-line application, install **`wenyi-cli`** instead; it
8
+ installs this package automatically. Core does not install the Web API or Desktop
9
+ application.
10
+
11
+ PDF layout preservation uses an optional, independently installed
12
+ `wenyi-babeldoc-bridge` service over HTTP. BabelDOC is not a dependency of this
13
+ package.
14
+
15
+ - [Source and license](https://github.com/BigDawnGhost/wenyi)
16
+ - [Architecture](https://github.com/BigDawnGhost/wenyi/blob/main/docs/architecture.md)
17
+ - [CLI guide](https://github.com/BigDawnGhost/wenyi/blob/main/docs/cli.md)
18
+ - [简体中文](https://github.com/BigDawnGhost/wenyi/blob/main/docs/zh/README.md)
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "wenyi-core"
3
+ dynamic = ["version"]
4
+ description = "Shared translation engine for multilingual long-form documents."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "openai>=1.40",
9
+ "pydantic>=2.7",
10
+ "pyyaml>=6.0",
11
+ "tenacity>=8.2",
12
+ "beautifulsoup4>=4.12",
13
+ "httpx>=0.27",
14
+ "lxml>=5.0",
15
+ "ebooklib>=0.18",
16
+ "pypdf>=6.14.2",
17
+ "google-genai>=2.13.0",
18
+ "json-repair>=0.61,<1",
19
+ "python-docx>=1.2.0",
20
+ "tiktoken>=0.14.0",
21
+ ]
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/BigDawnGhost/wenyi"
25
+ Documentation = "https://github.com/BigDawnGhost/wenyi/blob/main/docs/architecture.md"
26
+
27
+ [build-system]
28
+ requires = ["hatchling", "hatch-vcs"]
29
+ build-backend = "hatchling.build"
30
+
31
+ [tool.hatch.version]
32
+ source = "vcs"
33
+
34
+ [tool.hatch.version.raw-options]
35
+ root = "../.."
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["wenyi_core"]
File without changes
@@ -0,0 +1,31 @@
1
+ """Shared pytest fixtures for core tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ import pytest
8
+
9
+ _ANSI_RE = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]|\x1b\].*?\x07|\r")
10
+
11
+
12
+ def plain_text(value: str) -> str:
13
+ """Strip ANSI/control sequences so CLI assertions stay stable under Rich."""
14
+ return _ANSI_RE.sub("", value)
15
+
16
+
17
+ @pytest.fixture(autouse=True)
18
+ def _plain_rich_console(monkeypatch: pytest.MonkeyPatch) -> None:
19
+ """Prefer colorless consoles in tests; Progress may still emit markup."""
20
+ monkeypatch.setenv("NO_COLOR", "1")
21
+ monkeypatch.delenv("FORCE_COLOR", raising=False)
22
+
23
+ from rich.console import Console as RichConsole
24
+
25
+ def console_factory(*args, **kwargs):
26
+ kwargs = dict(kwargs)
27
+ kwargs["no_color"] = True
28
+ kwargs.setdefault("force_terminal", False)
29
+ return RichConsole(*args, **kwargs)
30
+
31
+ monkeypatch.setattr("rich.console.Console", console_factory)
@@ -0,0 +1,117 @@
1
+ """Offline FakeClient handler routing agent tasks through the complete pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+
8
+ from wenyi_core.llm.providers.fake import FakeClient
9
+ from wenyi_core.llm.usage import UsageSample
10
+
11
+ METERED_TOTAL_TOKENS = 8 # prompt 5 + completion 3, recorded by MeteredFakeClient per call.
12
+
13
+
14
+ def _count_numbered(text: str) -> int:
15
+ return len(re.findall(r"^\[(\d+)\]", text, re.MULTILINE))
16
+
17
+
18
+ def routing_handler(messages, tier, json_mode):
19
+ system = messages[0]["content"]
20
+ user = messages[-1]["content"]
21
+
22
+ if "language detector" in system:
23
+ return json.dumps({"language": "ja"}, ensure_ascii=False)
24
+
25
+ if "pre-translation analyst" in system:
26
+ return json.dumps(
27
+ {
28
+ "genre": "校园",
29
+ "tone": "冷峻",
30
+ "style_guide": "克制",
31
+ "characters": [{"source": "綾小路", "gender": "male"}],
32
+ },
33
+ ensure_ascii=False,
34
+ )
35
+
36
+ if "chapter title translator" in system:
37
+ n = _count_numbered(user)
38
+ return json.dumps({"titles": [f"标题{i}" for i in range(n)]}, ensure_ascii=False)
39
+
40
+ if "literary translator" in system:
41
+ # Polish may append a user turn to the same translation conversation.
42
+ if "Polish the translations from your previous JSON response" in user:
43
+ n = None
44
+ for message in reversed(messages[:-1]):
45
+ if message.get("role") == "assistant":
46
+ try:
47
+ payload = json.loads(message["content"])
48
+ except json.JSONDecodeError:
49
+ payload = {}
50
+ translations = payload.get("translations")
51
+ if isinstance(translations, list):
52
+ n = len(translations)
53
+ break
54
+ if n is None:
55
+ match = re.search(r"exactly (\d+) items", user)
56
+ n = int(match.group(1)) if match else 0
57
+ return json.dumps({"polished": [f"润{i}" for i in range(n)]}, ensure_ascii=False)
58
+ n = _count_numbered(user)
59
+ return json.dumps({"translations": [f"译{i}" for i in range(n)]}, ensure_ascii=False)
60
+
61
+ if "prose editor" in system:
62
+ n = _count_numbered(user)
63
+ return json.dumps({"polished": [f"润{i}" for i in range(n)]}, ensure_ascii=False)
64
+
65
+ if "translation reviewer" in system:
66
+ n = _count_numbered(user)
67
+ return json.dumps(
68
+ {
69
+ "issues": [],
70
+ "reviewed_segments": n,
71
+ "complete": True,
72
+ },
73
+ ensure_ascii=False,
74
+ )
75
+
76
+ if "terminology" in system and "extractor" in system:
77
+ return json.dumps(
78
+ {"terms": [{"source": "堀北", "target": "堀北", "type": "person", "gender": "female"}]},
79
+ ensure_ascii=False,
80
+ )
81
+
82
+ if "chapter digest writer" in system:
83
+ return "本章梗概:人物登场,情节推进。"
84
+
85
+ if "whole-book synopsis writer" in system:
86
+ return "全书概览:主线与人物关系,整体基调。"
87
+
88
+ return "{}" if json_mode else ""
89
+
90
+
91
+ class MeteredFakeClient(FakeClient):
92
+ """Record fixed small usage per offline call so tests can assert stage-level accounting."""
93
+
94
+ def complete(
95
+ self,
96
+ messages,
97
+ *,
98
+ operation,
99
+ json_mode=False,
100
+ max_tokens=None,
101
+ ):
102
+ self.usage.record(
103
+ self.routes[operation].tier or "direct",
104
+ UsageSample(
105
+ prompt_tokens=5,
106
+ completion_tokens=3,
107
+ total_tokens=8,
108
+ cache_miss_tokens=5,
109
+ ),
110
+ operation,
111
+ )
112
+ return super().complete(
113
+ messages,
114
+ operation=operation,
115
+ json_mode=json_mode,
116
+ max_tokens=max_tokens,
117
+ )
@@ -0,0 +1,26 @@
1
+ """Explicit routing graphs for transport tests with injected SDK responses."""
2
+
3
+ from wenyi_core.llm.configuration import LLMConfig
4
+ from wenyi_core.llm.registry import provider_spec
5
+
6
+
7
+ def model_config(*, kind="deepseek", profiles=None, **connection):
8
+ factory = getattr(provider_spec(kind)._module(), "preset_models", None)
9
+ defaults = factory() if factory else {}
10
+ profiles = profiles or {}
11
+ models = {}
12
+ for tier in ("strong", "cheap", "fast"):
13
+ default = defaults.get(tier)
14
+ profile = profiles.get(tier, {})
15
+ models[tier] = {
16
+ "provider": "default",
17
+ "model": profile.get("model") or (default.model if default else "test-model"),
18
+ "options": profile.get("options", default.options.model_dump() if default else {}),
19
+ }
20
+ return LLMConfig.model_validate(
21
+ {
22
+ "providers": {"default": {"kind": kind, **connection}},
23
+ "models": models,
24
+ "tiers": {tier: tier for tier in models},
25
+ }
26
+ )
@@ -0,0 +1,55 @@
1
+ """Shared configuration and storage narrowing for offline pipeline tests."""
2
+
3
+ from wenyi_core.config import Config
4
+ from wenyi_core.models.document import Segment
5
+ from wenyi_core.pipeline.translate.translation_batch import BatchPlan
6
+ from wenyi_core.storage.file import FileStorage
7
+ from wenyi_core.storage.protocol import Storage
8
+
9
+
10
+ def require_file_storage(store: Storage) -> FileStorage:
11
+ """CLI/offline tests use the file backend; narrow Storage to FileStorage for path asserts."""
12
+ if not isinstance(store, FileStorage):
13
+ raise TypeError(f"expected FileStorage, got {type(store).__name__}")
14
+ return store
15
+
16
+
17
+ def fake_agent_config() -> Config:
18
+ return Config.from_dict(
19
+ {
20
+ "language": {"source": "ja", "target": "zh"},
21
+ "llm": {
22
+ "preset": "fake",
23
+ "models": {
24
+ "default_strong": {"provider": "default", "model": "p"},
25
+ "default_cheap": {"provider": "default", "model": "f"},
26
+ },
27
+ },
28
+ }
29
+ )
30
+
31
+
32
+ def batch_plan(sources, *, allow_empty=False):
33
+ return BatchPlan.capture(
34
+ 3,
35
+ 5,
36
+ [Segment(index=10 + i, source=s) for i, s in enumerate(sources)],
37
+ [],
38
+ "previous translation",
39
+ "style",
40
+ "synopsis",
41
+ "digest",
42
+ [[] for _ in sources],
43
+ "next source",
44
+ allow_empty_translations=allow_empty,
45
+ )
46
+
47
+
48
+ def fake_pipeline_config(state_dir: str) -> Config:
49
+ config = fake_agent_config()
50
+ config.segment.max_tokens_per_batch = 1800
51
+ config.pipeline.review = True
52
+ config.pipeline.review_autofix = False
53
+ config.pipeline.polish = True
54
+ config.state_dir = state_dir
55
+ return config
@@ -0,0 +1,106 @@
1
+ """Shared fixed-book fixtures and fake responses for precision tests."""
2
+
3
+ import json
4
+ from collections import Counter
5
+ from pathlib import Path
6
+ from threading import Lock
7
+
8
+ from wenyi_core.config import Config
9
+ from wenyi_core.models.document import Chapter, Segment
10
+ from wenyi_core.pipeline.translate.translation_batch import BatchPlan
11
+ from wenyi_core.storage.file import FileStorage
12
+ from wenyi_core.storage.run_store import STATUS_PENDING
13
+
14
+ from tests.fake_llm import routing_handler
15
+
16
+
17
+ def precision_config(tmp_path: Path) -> Config:
18
+ return Config.from_dict(
19
+ {
20
+ "language": {"source": "en", "target": "zh"},
21
+ "llm": {"preset": "fake"},
22
+ "pipeline": {
23
+ "translation_mode": "best_of_three",
24
+ "review": False,
25
+ "book_understanding": False,
26
+ "annotation_alignment": False,
27
+ },
28
+ "paths": {"state_dir": str(tmp_path)},
29
+ }
30
+ )
31
+
32
+
33
+ def precision_store(tmp_path: Path, sources: tuple[str, ...] = ("one", "two")) -> FileStorage:
34
+ store = FileStorage(str(tmp_path / "book"))
35
+ store.save_chapter(
36
+ Chapter(
37
+ index=0,
38
+ segments=[Segment(index=i + 10, source=source) for i, source in enumerate(sources)],
39
+ )
40
+ )
41
+ store.save_manifest(
42
+ {
43
+ "title": "book",
44
+ "source_lang": "en",
45
+ "target_lang": "zh",
46
+ "source_sha256": "0" * 64,
47
+ "chapters": [{"index": 0, "status": STATUS_PENDING}],
48
+ }
49
+ )
50
+ return store
51
+
52
+
53
+ def artifact(store: FileStorage, key: str) -> dict:
54
+ value = store.read_artifact(key)
55
+ assert isinstance(value, dict), f"Expected a JSON object at {key}"
56
+ return value
57
+
58
+
59
+ def precision_plan(store: FileStorage, allow_empty: bool = False) -> BatchPlan:
60
+ segments = store.load_chapter(0).text_segments
61
+ return BatchPlan.capture(
62
+ 0,
63
+ 0,
64
+ segments,
65
+ [],
66
+ "prior",
67
+ "style",
68
+ "synopsis",
69
+ "digest",
70
+ [[] for _ in segments],
71
+ "following",
72
+ allow_empty_translations=allow_empty,
73
+ )
74
+
75
+
76
+ class PrecisionHandler:
77
+ def __init__(self, invalid=False, blank=False):
78
+ self.counts, self.lock = Counter(), Lock()
79
+ self.invalid, self.blank = invalid, blank
80
+
81
+ def __call__(self, messages, tier, json_mode):
82
+ task_message = next(
83
+ (message for message in messages if "Task (JSON):\n" in message["content"]), None
84
+ )
85
+ if task_message is None:
86
+ return routing_handler(messages, tier, json_mode)
87
+ sources = json.loads(messages[1]["content"].split("\n", 1)[1])["sources"]
88
+ task = json.loads(task_message["content"].split("Task (JSON):\n")[1])
89
+ kind = "synthesis" if "drafts" in task else "translate"
90
+ with self.lock:
91
+ self.counts[kind] += 1
92
+ sample = self.counts[kind]
93
+ if kind == "synthesis" and self.invalid:
94
+ return '{"translations":[]}'
95
+ return json.dumps(
96
+ {
97
+ "translations": [
98
+ source
99
+ if not any(character.isalpha() for character in source)
100
+ else ""
101
+ if self.blank
102
+ else f"{'润' if kind == 'synthesis' else '译'}{sample}:{i}"
103
+ for i, source in enumerate(sources)
104
+ ]
105
+ }
106
+ )
@@ -0,0 +1,130 @@
1
+ """Small book and fake-model configuration builders for review tests."""
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+
7
+ from wenyi_core.config import Config
8
+ from wenyi_core.models.document import Chapter, Segment
9
+ from wenyi_core.models.glossary import GlossaryTerm
10
+ from wenyi_core.review.evidence import BookEvidenceIndex
11
+ from wenyi_core.storage.file import FileStorage
12
+ from wenyi_core.storage.run_store import STATUS_DONE
13
+
14
+
15
+ def _config() -> Config:
16
+ return Config.from_dict(
17
+ {
18
+ "language": {"source": "en", "target": "zh"},
19
+ "llm": {
20
+ "preset": "fake",
21
+ "models": {
22
+ "default_strong": {"provider": "default", "model": "strong"},
23
+ "default_cheap": {"provider": "default", "model": "cheap"},
24
+ },
25
+ },
26
+ "pipeline": {
27
+ "review_agent_max_evidence_rounds": 2,
28
+ },
29
+ }
30
+ )
31
+
32
+
33
+ def _chapter(index: int, texts: list[tuple[str, str]]) -> Chapter:
34
+ return Chapter(
35
+ index=index,
36
+ title=f"Chapter {index}",
37
+ segments=[
38
+ Segment(index=segment_index, source=source, target=target)
39
+ for segment_index, (source, target) in enumerate(texts)
40
+ ],
41
+ meta={"source_digest": f"Digest {index}"},
42
+ )
43
+
44
+
45
+ def review_evidence() -> BookEvidenceIndex:
46
+ return BookEvidenceIndex(
47
+ [
48
+ _chapter(
49
+ 0,
50
+ [
51
+ ("Ann arrived.", "安到了。"),
52
+ ("Ann spoke.", "安开口了。"),
53
+ ],
54
+ )
55
+ ],
56
+ [GlossaryTerm(source="Ann", target="安", type="person")],
57
+ {},
58
+ )
59
+
60
+
61
+ def autofix_config(state_dir: str) -> Config:
62
+ return Config.from_dict(
63
+ {
64
+ "language": {"source": "ja", "target": "zh"},
65
+ "llm": {
66
+ "preset": "fake",
67
+ "models": {"default_strong": {"provider": "default", "model": "p"}},
68
+ },
69
+ "pipeline": {
70
+ "review_autofix": True,
71
+ "review_concurrency": 1,
72
+ },
73
+ "output": {"punctuation_normalize": False},
74
+ "paths": {"state_dir": state_dir},
75
+ }
76
+ )
77
+
78
+
79
+ def autofix_store(directory: str, target: str = "正式译文。") -> FileStorage:
80
+ store = FileStorage(str(Path(directory, "state", "book")))
81
+ store.save_chapter(
82
+ Chapter(
83
+ index=0,
84
+ title="第一章",
85
+ segments=[Segment(index=0, source="原文。", target=target)],
86
+ )
87
+ )
88
+ store.save_manifest(
89
+ {
90
+ "title": "book",
91
+ "source_lang": "ja",
92
+ "target_lang": "zh",
93
+ "source_sha256": "0" * 64,
94
+ "chapters": [{"index": 0, "status": STATUS_DONE}],
95
+ }
96
+ )
97
+ return store
98
+
99
+
100
+ def review_json(user: str, issues: list[dict]) -> str:
101
+ """Build a reviewer response with its completeness receipt."""
102
+ return json.dumps(
103
+ {
104
+ "issues": issues,
105
+ "reviewed_segments": len(re.findall(r"^\[(\d+)\]", user, re.MULTILINE)),
106
+ "complete": True,
107
+ },
108
+ ensure_ascii=False,
109
+ )
110
+
111
+
112
+ def fix_json(user: str, replacement: str) -> str:
113
+ """Echo identity fields from a fixer request into a complete temporary replacement."""
114
+
115
+ def field(name: str) -> str:
116
+ match = re.search(rf"^{name}:\s*(.+)$", user, re.MULTILINE)
117
+ if match is None:
118
+ raise AssertionError(f"Fixer prompt missing {name}")
119
+ return match.group(1).strip()
120
+
121
+ return json.dumps(
122
+ {
123
+ "segment_ref": field("segment_ref"),
124
+ "before_hash": field("before_hash"),
125
+ "issue_ids": json.loads(field("issue_ids")),
126
+ "replacement": replacement,
127
+ "complete": True,
128
+ },
129
+ ensure_ascii=False,
130
+ )