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.
- wenyi_core-1.4.2/.gitignore +3 -0
- wenyi_core-1.4.2/PKG-INFO +40 -0
- wenyi_core-1.4.2/README.md +18 -0
- wenyi_core-1.4.2/pyproject.toml +38 -0
- wenyi_core-1.4.2/tests/__init__.py +0 -0
- wenyi_core-1.4.2/tests/conftest.py +31 -0
- wenyi_core-1.4.2/tests/fake_llm.py +117 -0
- wenyi_core-1.4.2/tests/model_fixtures.py +26 -0
- wenyi_core-1.4.2/tests/pipeline_fixtures.py +55 -0
- wenyi_core-1.4.2/tests/precision_fixtures.py +106 -0
- wenyi_core-1.4.2/tests/review_fixtures.py +130 -0
- wenyi_core-1.4.2/tests/sample_data.py +450 -0
- wenyi_core-1.4.2/tests/storage_fixtures.py +61 -0
- wenyi_core-1.4.2/tests/terminology_fixtures.py +180 -0
- wenyi_core-1.4.2/tests/test_annotation_aligner.py +445 -0
- wenyi_core-1.4.2/tests/test_annotations.py +138 -0
- wenyi_core-1.4.2/tests/test_architecture_boundaries.py +748 -0
- wenyi_core-1.4.2/tests/test_assemble.py +1765 -0
- wenyi_core-1.4.2/tests/test_atomicio.py +87 -0
- wenyi_core-1.4.2/tests/test_babeldoc_bridge_client.py +248 -0
- wenyi_core-1.4.2/tests/test_babeldoc_context.py +487 -0
- wenyi_core-1.4.2/tests/test_babeldoc_fillback.py +50 -0
- wenyi_core-1.4.2/tests/test_bilingual.py +569 -0
- wenyi_core-1.4.2/tests/test_book_understanding.py +431 -0
- wenyi_core-1.4.2/tests/test_cache_accounting.py +266 -0
- wenyi_core-1.4.2/tests/test_ci_gates.py +284 -0
- wenyi_core-1.4.2/tests/test_config.py +217 -0
- wenyi_core-1.4.2/tests/test_desktop_updates.py +216 -0
- wenyi_core-1.4.2/tests/test_docx.py +661 -0
- wenyi_core-1.4.2/tests/test_docx_bilingual.py +183 -0
- wenyi_core-1.4.2/tests/test_errors.py +179 -0
- wenyi_core-1.4.2/tests/test_export_guards.py +320 -0
- wenyi_core-1.4.2/tests/test_file_artifacts.py +322 -0
- wenyi_core-1.4.2/tests/test_file_migrations.py +102 -0
- wenyi_core-1.4.2/tests/test_file_storage.py +463 -0
- wenyi_core-1.4.2/tests/test_glossary.py +227 -0
- wenyi_core-1.4.2/tests/test_glossary_agents.py +413 -0
- wenyi_core-1.4.2/tests/test_glossary_anki.py +206 -0
- wenyi_core-1.4.2/tests/test_glossary_history_cache.py +272 -0
- wenyi_core-1.4.2/tests/test_glossary_pagination.py +51 -0
- wenyi_core-1.4.2/tests/test_glossary_readings.py +194 -0
- wenyi_core-1.4.2/tests/test_glossary_snapshot_checkpoint_race.py +62 -0
- wenyi_core-1.4.2/tests/test_i18n.py +401 -0
- wenyi_core-1.4.2/tests/test_ingest.py +1654 -0
- wenyi_core-1.4.2/tests/test_ingest_encoding.py +79 -0
- wenyi_core-1.4.2/tests/test_input_preparation.py +306 -0
- wenyi_core-1.4.2/tests/test_language_detection_errors.py +105 -0
- wenyi_core-1.4.2/tests/test_language_policy.py +254 -0
- wenyi_core-1.4.2/tests/test_language_policy_resume.py +185 -0
- wenyi_core-1.4.2/tests/test_language_policy_text.py +190 -0
- wenyi_core-1.4.2/tests/test_llm.py +502 -0
- wenyi_core-1.4.2/tests/test_llm_capture.py +181 -0
- wenyi_core-1.4.2/tests/test_llm_connectivity.py +233 -0
- wenyi_core-1.4.2/tests/test_llm_gemini.py +340 -0
- wenyi_core-1.4.2/tests/test_llm_models.py +144 -0
- wenyi_core-1.4.2/tests/test_llm_retrying.py +328 -0
- wenyi_core-1.4.2/tests/test_llm_routing.py +657 -0
- wenyi_core-1.4.2/tests/test_llm_safe_errors.py +217 -0
- wenyi_core-1.4.2/tests/test_locks_contract.py +427 -0
- wenyi_core-1.4.2/tests/test_metadata_language.py +131 -0
- wenyi_core-1.4.2/tests/test_mineru_credentials.py +218 -0
- wenyi_core-1.4.2/tests/test_newfeatures.py +345 -0
- wenyi_core-1.4.2/tests/test_nginx_proxy.py +177 -0
- wenyi_core-1.4.2/tests/test_observability.py +117 -0
- wenyi_core-1.4.2/tests/test_opencode_go.py +64 -0
- wenyi_core-1.4.2/tests/test_orchestrator.py +2779 -0
- wenyi_core-1.4.2/tests/test_orchestrator_contract.py +431 -0
- wenyi_core-1.4.2/tests/test_pdf_export_defaults.py +288 -0
- wenyi_core-1.4.2/tests/test_pdf_support.py +606 -0
- wenyi_core-1.4.2/tests/test_pdf_to_html_client.py +85 -0
- wenyi_core-1.4.2/tests/test_precision_agent.py +297 -0
- wenyi_core-1.4.2/tests/test_precision_archive.py +351 -0
- wenyi_core-1.4.2/tests/test_precision_configuration.py +151 -0
- wenyi_core-1.4.2/tests/test_precision_drafts.py +208 -0
- wenyi_core-1.4.2/tests/test_precision_pipeline.py +355 -0
- wenyi_core-1.4.2/tests/test_precision_records.py +363 -0
- wenyi_core-1.4.2/tests/test_preparation.py +305 -0
- wenyi_core-1.4.2/tests/test_preparation_modes.py +158 -0
- wenyi_core-1.4.2/tests/test_prompt_evidence.py +205 -0
- wenyi_core-1.4.2/tests/test_provider_errors.py +82 -0
- wenyi_core-1.4.2/tests/test_release_packaging.py +533 -0
- wenyi_core-1.4.2/tests/test_review_actions.py +262 -0
- wenyi_core-1.4.2/tests/test_review_agent.py +1538 -0
- wenyi_core-1.4.2/tests/test_review_arbiter_prompt.py +190 -0
- wenyi_core-1.4.2/tests/test_review_autofix.py +597 -0
- wenyi_core-1.4.2/tests/test_review_checkpoint.py +147 -0
- wenyi_core-1.4.2/tests/test_review_evidence.py +255 -0
- wenyi_core-1.4.2/tests/test_review_fixer.py +190 -0
- wenyi_core-1.4.2/tests/test_review_polish.py +545 -0
- wenyi_core-1.4.2/tests/test_review_run_store.py +263 -0
- wenyi_core-1.4.2/tests/test_review_session.py +235 -0
- wenyi_core-1.4.2/tests/test_rolling_context.py +63 -0
- wenyi_core-1.4.2/tests/test_routing_resume.py +396 -0
- wenyi_core-1.4.2/tests/test_run_report.py +172 -0
- wenyi_core-1.4.2/tests/test_runtime_usage.py +216 -0
- wenyi_core-1.4.2/tests/test_sqlite_storage.py +919 -0
- wenyi_core-1.4.2/tests/test_storage_injection.py +165 -0
- wenyi_core-1.4.2/tests/test_terminology_agent.py +232 -0
- wenyi_core-1.4.2/tests/test_terminology_citations.py +186 -0
- wenyi_core-1.4.2/tests/test_terminology_discovery.py +236 -0
- wenyi_core-1.4.2/tests/test_terminology_entrypoint.py +214 -0
- wenyi_core-1.4.2/tests/test_terminology_evidence.py +67 -0
- wenyi_core-1.4.2/tests/test_terminology_person_scope.py +203 -0
- wenyi_core-1.4.2/tests/test_terminology_pipeline.py +430 -0
- wenyi_core-1.4.2/tests/test_terminology_policy.py +159 -0
- wenyi_core-1.4.2/tests/test_terminology_service.py +326 -0
- wenyi_core-1.4.2/tests/test_terminology_transport.py +121 -0
- wenyi_core-1.4.2/tests/test_terminology_validation.py +159 -0
- wenyi_core-1.4.2/tests/test_test_layout.py +75 -0
- wenyi_core-1.4.2/tests/test_timing.py +210 -0
- wenyi_core-1.4.2/tests/test_title_translation.py +184 -0
- wenyi_core-1.4.2/tests/test_translation_batch.py +253 -0
- wenyi_core-1.4.2/tests/test_translation_context.py +575 -0
- wenyi_core-1.4.2/tests/test_translator.py +336 -0
- wenyi_core-1.4.2/tests/test_usage.py +1028 -0
- wenyi_core-1.4.2/wenyi_core/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/agents/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/agents/analyzer.py +109 -0
- wenyi_core-1.4.2/wenyi_core/agents/annotation_aligner.py +521 -0
- wenyi_core-1.4.2/wenyi_core/agents/base.py +223 -0
- wenyi_core-1.4.2/wenyi_core/agents/glossary_extractor.py +358 -0
- wenyi_core-1.4.2/wenyi_core/agents/polisher.py +98 -0
- wenyi_core-1.4.2/wenyi_core/agents/precision.py +186 -0
- wenyi_core-1.4.2/wenyi_core/agents/prompts.py +105 -0
- wenyi_core-1.4.2/wenyi_core/agents/review_actions.py +389 -0
- wenyi_core-1.4.2/wenyi_core/agents/review_arbiter.py +211 -0
- wenyi_core-1.4.2/wenyi_core/agents/review_fixer.py +354 -0
- wenyi_core-1.4.2/wenyi_core/agents/review_loop.py +268 -0
- wenyi_core-1.4.2/wenyi_core/agents/reviewer.py +173 -0
- wenyi_core-1.4.2/wenyi_core/agents/synopsis.py +157 -0
- wenyi_core-1.4.2/wenyi_core/agents/terminology.py +429 -0
- wenyi_core-1.4.2/wenyi_core/agents/title_translator.py +41 -0
- wenyi_core-1.4.2/wenyi_core/agents/translator.py +281 -0
- wenyi_core-1.4.2/wenyi_core/assemble/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/assemble/about.py +136 -0
- wenyi_core-1.4.2/wenyi_core/assemble/docx_blocks.py +354 -0
- wenyi_core-1.4.2/wenyi_core/assemble/docx_numbering.py +83 -0
- wenyi_core-1.4.2/wenyi_core/assemble/docx_styles.py +250 -0
- wenyi_core-1.4.2/wenyi_core/assemble/docx_writer.py +69 -0
- wenyi_core-1.4.2/wenyi_core/assemble/epub_navigation.py +199 -0
- wenyi_core-1.4.2/wenyi_core/assemble/epub_presentation.py +152 -0
- wenyi_core-1.4.2/wenyi_core/assemble/epub_resources.py +197 -0
- wenyi_core-1.4.2/wenyi_core/assemble/epub_writer.py +388 -0
- wenyi_core-1.4.2/wenyi_core/assemble/export_view.py +156 -0
- wenyi_core-1.4.2/wenyi_core/assemble/html_bilingual.py +214 -0
- wenyi_core-1.4.2/wenyi_core/assemble/html_inline.py +344 -0
- wenyi_core-1.4.2/wenyi_core/assemble/html_renderer.py +237 -0
- wenyi_core-1.4.2/wenyi_core/assemble/html_resources.py +230 -0
- wenyi_core-1.4.2/wenyi_core/assemble/html_writer.py +175 -0
- wenyi_core-1.4.2/wenyi_core/assemble/pdf_writer.py +18 -0
- wenyi_core-1.4.2/wenyi_core/assemble/policy.py +93 -0
- wenyi_core-1.4.2/wenyi_core/assemble/text_writer.py +63 -0
- wenyi_core-1.4.2/wenyi_core/assemble/writer.py +179 -0
- wenyi_core-1.4.2/wenyi_core/assemble/writer_common.py +185 -0
- wenyi_core-1.4.2/wenyi_core/atomicio.py +82 -0
- wenyi_core-1.4.2/wenyi_core/config.py +405 -0
- wenyi_core-1.4.2/wenyi_core/document_styles/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/document_styles/docx.py +147 -0
- wenyi_core-1.4.2/wenyi_core/errors.py +59 -0
- wenyi_core-1.4.2/wenyi_core/events.py +67 -0
- wenyi_core-1.4.2/wenyi_core/glossary/__init__.py +5 -0
- wenyi_core-1.4.2/wenyi_core/glossary/anki.py +317 -0
- wenyi_core-1.4.2/wenyi_core/glossary/evidence.py +111 -0
- wenyi_core-1.4.2/wenyi_core/glossary/evidence_models.py +46 -0
- wenyi_core-1.4.2/wenyi_core/glossary/resolver.py +18 -0
- wenyi_core-1.4.2/wenyi_core/glossary/store.py +487 -0
- wenyi_core-1.4.2/wenyi_core/i18n/__init__.py +5 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/export/about.en.xhtml +18 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/export/about.zh.xhtml +39 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/de.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/en-GB.json +6 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/en-US.json +6 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/en.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/es.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/fr.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/it.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/ja.json +19 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/ko.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/pt-BR.json +6 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/pt-PT.json +6 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/pt.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/registry.json +97 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/ru.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/vi.json +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/zh-Hant.json +19 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/languages/zh.json +24 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/pairs/ja__zh.json +5 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/pairs/registry.json +3 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/shared/guidance.json +4 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/shared/honorific.json +5 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/shared/metadata_guidance.txt +4 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/shared/review_evidence_tools.txt +14 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/analyzer_system.txt +17 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/analyzer_user.txt +5 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/book_synopsis_system.txt +3 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/book_synopsis_user.txt +7 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/chapter_digest_system.txt +2 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/chapter_digest_user.txt +4 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_extractor_system.txt +16 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_extractor_user.txt +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_history_system.txt +6 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/glossary_history_user.txt +4 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/language_detector_system.txt +3 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_continue_user.txt +10 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_system.txt +6 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_terminology_user.txt +15 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/polisher_user.txt +13 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_generation_system.txt +9 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_packet.txt +2 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_synthesize.txt +5 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/precision_translate.txt +3 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_agent_system.txt +32 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_agent_user.txt +12 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_arbiter_system.txt +21 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_arbiter_user.txt +4 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_fixer_system.txt +12 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/review_fixer_user.txt +30 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/reviewer_system.txt +11 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/reviewer_user.txt +7 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_discover_system.txt +4 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_discover_user.txt +2 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_evidence_system.txt +5 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_evidence_user.txt +2 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_merge_system.txt +4 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/terminology_merge_user.txt +2 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/title_translator_system.txt +7 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/title_translator_user.txt +7 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/translator_system.txt +11 -0
- wenyi_core-1.4.2/wenyi_core/i18n/data/tasks/translator_user.txt +25 -0
- wenyi_core-1.4.2/wenyi_core/i18n/languages.py +96 -0
- wenyi_core-1.4.2/wenyi_core/i18n/metadata.py +13 -0
- wenyi_core-1.4.2/wenyi_core/i18n/policy/__init__.py +6 -0
- wenyi_core-1.4.2/wenyi_core/i18n/policy/models.py +225 -0
- wenyi_core-1.4.2/wenyi_core/i18n/policy/registry.py +99 -0
- wenyi_core-1.4.2/wenyi_core/i18n/policy/resolver.py +381 -0
- wenyi_core-1.4.2/wenyi_core/i18n/policy/tasks.py +40 -0
- wenyi_core-1.4.2/wenyi_core/i18n/prompts.py +48 -0
- wenyi_core-1.4.2/wenyi_core/i18n/resources.py +28 -0
- wenyi_core-1.4.2/wenyi_core/ingest/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/ingest/docx_reader.py +562 -0
- wenyi_core-1.4.2/wenyi_core/ingest/encoding.py +84 -0
- wenyi_core-1.4.2/wenyi_core/ingest/epub_chapters.py +67 -0
- wenyi_core-1.4.2/wenyi_core/ingest/epub_layout.py +360 -0
- wenyi_core-1.4.2/wenyi_core/ingest/epub_package.py +126 -0
- wenyi_core-1.4.2/wenyi_core/ingest/epub_reader.py +120 -0
- wenyi_core-1.4.2/wenyi_core/ingest/epub_toc.py +205 -0
- wenyi_core-1.4.2/wenyi_core/ingest/errors.py +15 -0
- wenyi_core-1.4.2/wenyi_core/ingest/fb2_reader.py +319 -0
- wenyi_core-1.4.2/wenyi_core/ingest/html_reader.py +182 -0
- wenyi_core-1.4.2/wenyi_core/ingest/models.py +17 -0
- wenyi_core-1.4.2/wenyi_core/ingest/pdf_babeldoc.py +368 -0
- wenyi_core-1.4.2/wenyi_core/ingest/pdf_reader.py +124 -0
- wenyi_core-1.4.2/wenyi_core/ingest/pdf_to_html.py +365 -0
- wenyi_core-1.4.2/wenyi_core/ingest/segmenter.py +216 -0
- wenyi_core-1.4.2/wenyi_core/ingest/source_hash.py +14 -0
- wenyi_core-1.4.2/wenyi_core/ingest/text_reader.py +98 -0
- wenyi_core-1.4.2/wenyi_core/ingest/tokens.py +31 -0
- wenyi_core-1.4.2/wenyi_core/llm/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/llm/base.py +147 -0
- wenyi_core-1.4.2/wenyi_core/llm/configuration.py +171 -0
- wenyi_core-1.4.2/wenyi_core/llm/connectivity.py +215 -0
- wenyi_core-1.4.2/wenyi_core/llm/credentials.py +22 -0
- wenyi_core-1.4.2/wenyi_core/llm/errors.py +93 -0
- wenyi_core-1.4.2/wenyi_core/llm/factory.py +14 -0
- wenyi_core-1.4.2/wenyi_core/llm/json_parser.py +120 -0
- wenyi_core-1.4.2/wenyi_core/llm/limits.py +135 -0
- wenyi_core-1.4.2/wenyi_core/llm/migration.py +84 -0
- wenyi_core-1.4.2/wenyi_core/llm/models.py +82 -0
- wenyi_core-1.4.2/wenyi_core/llm/operation_names.py +48 -0
- wenyi_core-1.4.2/wenyi_core/llm/operations.py +209 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/_openai_compatible.py +207 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/deepseek.py +108 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/fake.py +91 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/gemini.py +287 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/ollama.py +9 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/openai.py +73 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/openai_compatible.py +113 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/opencode_go.py +102 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/openrouter.py +73 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/orcarouter.py +12 -0
- wenyi_core-1.4.2/wenyi_core/llm/providers/vllm.py +9 -0
- wenyi_core-1.4.2/wenyi_core/llm/registry.py +146 -0
- wenyi_core-1.4.2/wenyi_core/llm/retrying.py +418 -0
- wenyi_core-1.4.2/wenyi_core/llm/router.py +286 -0
- wenyi_core-1.4.2/wenyi_core/llm/routing.py +159 -0
- wenyi_core-1.4.2/wenyi_core/llm/transport.py +121 -0
- wenyi_core-1.4.2/wenyi_core/llm/usage.py +325 -0
- wenyi_core-1.4.2/wenyi_core/markup/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/markup/anchors.py +108 -0
- wenyi_core-1.4.2/wenyi_core/markup/annotations.py +454 -0
- wenyi_core-1.4.2/wenyi_core/markup/contracts.py +56 -0
- wenyi_core-1.4.2/wenyi_core/markup/ruby.py +20 -0
- wenyi_core-1.4.2/wenyi_core/markup/segments.py +488 -0
- wenyi_core-1.4.2/wenyi_core/models/__init__.py +38 -0
- wenyi_core-1.4.2/wenyi_core/models/document.py +60 -0
- wenyi_core-1.4.2/wenyi_core/models/errors.py +26 -0
- wenyi_core-1.4.2/wenyi_core/models/glossary.py +80 -0
- wenyi_core-1.4.2/wenyi_core/observability.py +119 -0
- wenyi_core-1.4.2/wenyi_core/pdf_bridge/__init__.py +8 -0
- wenyi_core-1.4.2/wenyi_core/pdf_bridge/client.py +102 -0
- wenyi_core-1.4.2/wenyi_core/pdf_bridge/connection.py +35 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/__init__.py +111 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/annotate/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/annotate/annotations.py +285 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/annotate/docx_styles.py +153 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/autofix/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_candidates.py +269 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_plan.py +77 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_publish.py +198 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/autofix/autofix_verification.py +276 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/autofix/review_autofix.py +170 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/finalize/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/finalize/finalization.py +164 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/orchestrator.py +361 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/prepare/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/prepare/book_understanding.py +235 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/prepare/input_preparation.py +138 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/prepare/preparation.py +397 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/review/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/review/review_checkpoint.py +169 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/review/review_chunks.py +500 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/review/review_results.py +314 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/review/review_rounds.py +429 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/review/review_workflow.py +420 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/run/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/run/language_policies.py +52 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/run/runtime.py +178 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/run/runtime_usage.py +81 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/__init__.py +1 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/babeldoc_context.py +136 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/context.py +44 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/precision.py +427 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/precision_records.py +198 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/terminology.py +364 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/terminology_metadata.py +27 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/title_translation.py +239 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/translation.py +637 -0
- wenyi_core-1.4.2/wenyi_core/pipeline/translate/translation_batch.py +142 -0
- wenyi_core-1.4.2/wenyi_core/postprocess/__init__.py +0 -0
- wenyi_core-1.4.2/wenyi_core/postprocess/export_text.py +92 -0
- wenyi_core-1.4.2/wenyi_core/postprocess/punct.py +155 -0
- wenyi_core-1.4.2/wenyi_core/review/__init__.py +5 -0
- wenyi_core-1.4.2/wenyi_core/review/autofix_models.py +111 -0
- wenyi_core-1.4.2/wenyi_core/review/conflicts.py +173 -0
- wenyi_core-1.4.2/wenyi_core/review/contracts.py +36 -0
- wenyi_core-1.4.2/wenyi_core/review/evidence.py +361 -0
- wenyi_core-1.4.2/wenyi_core/review/models.py +144 -0
- wenyi_core-1.4.2/wenyi_core/review/run_store.py +463 -0
- wenyi_core-1.4.2/wenyi_core/review/session.py +396 -0
- wenyi_core-1.4.2/wenyi_core/storage/__init__.py +20 -0
- wenyi_core-1.4.2/wenyi_core/storage/artifacts.py +86 -0
- wenyi_core-1.4.2/wenyi_core/storage/atomic_write.py +134 -0
- wenyi_core-1.4.2/wenyi_core/storage/file.py +254 -0
- wenyi_core-1.4.2/wenyi_core/storage/file_migrations.py +38 -0
- wenyi_core-1.4.2/wenyi_core/storage/io_path.py +68 -0
- wenyi_core-1.4.2/wenyi_core/storage/language_policies.py +69 -0
- wenyi_core-1.4.2/wenyi_core/storage/locks.py +350 -0
- wenyi_core-1.4.2/wenyi_core/storage/names.py +49 -0
- wenyi_core-1.4.2/wenyi_core/storage/precision_archive.py +605 -0
- wenyi_core-1.4.2/wenyi_core/storage/precision_drafts.py +203 -0
- wenyi_core-1.4.2/wenyi_core/storage/protocol.py +98 -0
- wenyi_core-1.4.2/wenyi_core/storage/run_store.py +606 -0
- wenyi_core-1.4.2/wenyi_core/storage/sqlite.py +1035 -0
- wenyi_core-1.4.2/wenyi_core/timing.py +137 -0
|
@@ -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
|
+
)
|