mostlyright-data 0.9.0__py3-none-any.whl
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.
- mostlyright/data_harness/__init__.py +158 -0
- mostlyright/data_harness/acquisition/__init__.py +55 -0
- mostlyright/data_harness/acquisition/http.py +2773 -0
- mostlyright/data_harness/acquisition/parsing.py +809 -0
- mostlyright/data_harness/acquisition/ranges.py +495 -0
- mostlyright/data_harness/acquisition/result_download.py +360 -0
- mostlyright/data_harness/acquisition/retention_admission.py +248 -0
- mostlyright/data_harness/acquisition/sandbox.py +4888 -0
- mostlyright/data_harness/acquisition/url_policy.py +530 -0
- mostlyright/data_harness/agent_runtime.py +2743 -0
- mostlyright/data_harness/assets/logo-ink.svg +31 -0
- mostlyright/data_harness/backends/__init__.py +28 -0
- mostlyright/data_harness/backends/pandas_backend.py +350 -0
- mostlyright/data_harness/backends/polars_backend.py +366 -0
- mostlyright/data_harness/backends/protocol.py +124 -0
- mostlyright/data_harness/backends/reference.py +83 -0
- mostlyright/data_harness/backends/registry.py +55 -0
- mostlyright/data_harness/backends/restrictions.py +126 -0
- mostlyright/data_harness/canonical.py +333 -0
- mostlyright/data_harness/catalog_job.py +625 -0
- mostlyright/data_harness/cli.py +5398 -0
- mostlyright/data_harness/contracts.py +53 -0
- mostlyright/data_harness/coordinator.py +1307 -0
- mostlyright/data_harness/deploy.py +924 -0
- mostlyright/data_harness/deploy_target.py +312 -0
- mostlyright/data_harness/deployment_evidence.py +1067 -0
- mostlyright/data_harness/event_presentation.py +576 -0
- mostlyright/data_harness/events.py +2152 -0
- mostlyright/data_harness/fast_delimited.py +239 -0
- mostlyright/data_harness/fleet.py +237 -0
- mostlyright/data_harness/formats.py +236 -0
- mostlyright/data_harness/governors.py +1163 -0
- mostlyright/data_harness/hosted_bootstrap.py +972 -0
- mostlyright/data_harness/hosted_crawler.py +1115 -0
- mostlyright/data_harness/hosted_crawler_container_smoke.py +351 -0
- mostlyright/data_harness/hosted_crawler_fetch.py +423 -0
- mostlyright/data_harness/hosted_crawler_job.py +1277 -0
- mostlyright/data_harness/hosted_crawler_protocol.py +676 -0
- mostlyright/data_harness/hosted_dataset.py +1500 -0
- mostlyright/data_harness/hosted_deploy.py +3037 -0
- mostlyright/data_harness/hosted_handoff.py +62 -0
- mostlyright/data_harness/hosted_ingestion_contract.py +504 -0
- mostlyright/data_harness/hosted_ingestion_job.py +356 -0
- mostlyright/data_harness/hosted_ingestion_job_smoke.py +40 -0
- mostlyright/data_harness/hosted_session_container_smoke.py +194 -0
- mostlyright/data_harness/hosted_session_worker.py +3554 -0
- mostlyright/data_harness/hosted_session_worker_job_smoke.py +46 -0
- mostlyright/data_harness/hosted_worker.py +6784 -0
- mostlyright/data_harness/ingestion/__init__.py +56 -0
- mostlyright/data_harness/ingestion/contracts.py +461 -0
- mostlyright/data_harness/ingestion/faults.py +42 -0
- mostlyright/data_harness/ingestion/gcs_store.py +1162 -0
- mostlyright/data_harness/ingestion/spool.py +130 -0
- mostlyright/data_harness/ingestion/store.py +885 -0
- mostlyright/data_harness/key_seam.py +434 -0
- mostlyright/data_harness/linux_process_boundary.py +262 -0
- mostlyright/data_harness/local_contracts.py +2880 -0
- mostlyright/data_harness/local_search/__init__.py +5 -0
- mostlyright/data_harness/local_search/build_index.py +1087 -0
- mostlyright/data_harness/local_search/contracts.py +920 -0
- mostlyright/data_harness/local_search/query_trace.py +266 -0
- mostlyright/data_harness/local_search/retrieval.py +700 -0
- mostlyright/data_harness/local_search/sealed.py +474 -0
- mostlyright/data_harness/local_search/service.py +784 -0
- mostlyright/data_harness/nbrender/CONTRACT.md +212 -0
- mostlyright/data_harness/nbrender/__init__.py +12 -0
- mostlyright/data_harness/nbrender/chrome.py +359 -0
- mostlyright/data_harness/nbrender/code_body.py +266 -0
- mostlyright/data_harness/nbrender/document.py +407 -0
- mostlyright/data_harness/nbrender/frame.py +275 -0
- mostlyright/data_harness/nbrender/interactive.py +337 -0
- mostlyright/data_harness/nbrender/markdown_body.py +477 -0
- mostlyright/data_harness/nbrender/mr_components.py +134 -0
- mostlyright/data_harness/nbrender/outputs_data.py +595 -0
- mostlyright/data_harness/nbrender/outputs_rich.py +906 -0
- mostlyright/data_harness/nbrender/outputs_source.py +260 -0
- mostlyright/data_harness/nbrender/outputs_stage.py +176 -0
- mostlyright/data_harness/nbrender/outputs_text.py +400 -0
- mostlyright/data_harness/nbrender/parse.py +394 -0
- mostlyright/data_harness/nbrender/status.py +40 -0
- mostlyright/data_harness/nbrender/tokens.py +1295 -0
- mostlyright/data_harness/notebook.py +1710 -0
- mostlyright/data_harness/offline.py +2049 -0
- mostlyright/data_harness/operation_registry.py +1007 -0
- mostlyright/data_harness/operator_setup.py +239 -0
- mostlyright/data_harness/pipeline.py +6428 -0
- mostlyright/data_harness/plan_graph.py +2026 -0
- mostlyright/data_harness/preparation/__init__.py +104 -0
- mostlyright/data_harness/preparation/contracts.py +1017 -0
- mostlyright/data_harness/preparation/engine.py +221 -0
- mostlyright/data_harness/preparation/errors.py +14 -0
- mostlyright/data_harness/preparation/gates.py +751 -0
- mostlyright/data_harness/preparation/joins.py +574 -0
- mostlyright/data_harness/preparation/profile.py +384 -0
- mostlyright/data_harness/preparation/table.py +217 -0
- mostlyright/data_harness/preparation/transforms.py +568 -0
- mostlyright/data_harness/progress_events.py +534 -0
- mostlyright/data_harness/readers/__init__.py +46 -0
- mostlyright/data_harness/readers/containers.py +963 -0
- mostlyright/data_harness/readers/contracts.py +542 -0
- mostlyright/data_harness/readers/delimited.py +257 -0
- mostlyright/data_harness/readers/grib2/__init__.py +33 -0
- mostlyright/data_harness/readers/grib2/admission.py +722 -0
- mostlyright/data_harness/readers/grib2/decode.py +1009 -0
- mostlyright/data_harness/readers/grib2/geometry.py +1133 -0
- mostlyright/data_harness/readers/grib2/portable_math.py +501 -0
- mostlyright/data_harness/readers/json_tabular.py +485 -0
- mostlyright/data_harness/readers/registry.py +514 -0
- mostlyright/data_harness/readers/samples/README.md +110 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/cities.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/cities.csv +3 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/expected.json +23 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/cities.tsv +5 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/expected.json +30 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/observations.csv +5 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/expected.json +44 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/stations.json +1 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/expected.json +48 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/observations.ndjson +4 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/cities.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/expected.json +24 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/README.md +20 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/expected.json +55 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/gfs-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/hrrr-categorical-rain.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples.py +582 -0
- mostlyright/data_harness/readers/spreadsheet.py +803 -0
- mostlyright/data_harness/readers/tabular.py +510 -0
- mostlyright/data_harness/recipe.py +5321 -0
- mostlyright/data_harness/repair/__init__.py +78 -0
- mostlyright/data_harness/repair/adapters.py +274 -0
- mostlyright/data_harness/repair/contracts.py +872 -0
- mostlyright/data_harness/repair/coordinator.py +1099 -0
- mostlyright/data_harness/repair/errors.py +16 -0
- mostlyright/data_harness/review.py +2533 -0
- mostlyright/data_harness/rowset.py +283 -0
- mostlyright/data_harness/serving.py +1975 -0
- mostlyright/data_harness/serving_edge.py +590 -0
- mostlyright/data_harness/serving_http.py +1031 -0
- mostlyright/data_harness/session_probes.py +759 -0
- mostlyright/data_harness/signing.py +101 -0
- mostlyright/data_harness/source_discovery.py +898 -0
- mostlyright/data_harness/sources/__init__.py +209 -0
- mostlyright/data_harness/sources/_adapter_steps.py +213 -0
- mostlyright/data_harness/sources/adapters.py +1214 -0
- mostlyright/data_harness/sources/cadence.py +1428 -0
- mostlyright/data_harness/sources/cadence_emission.py +453 -0
- mostlyright/data_harness/sources/cadence_history.py +546 -0
- mostlyright/data_harness/sources/catalog/__init__.py +17 -0
- mostlyright/data_harness/sources/catalog/admission.py +477 -0
- mostlyright/data_harness/sources/catalog/authoring.py +1701 -0
- mostlyright/data_harness/sources/catalog/authoring_policy.py +701 -0
- mostlyright/data_harness/sources/catalog/authoring_shards.py +1217 -0
- mostlyright/data_harness/sources/catalog/bounded_io.py +231 -0
- mostlyright/data_harness/sources/catalog/channel.py +523 -0
- mostlyright/data_harness/sources/catalog/channel_client.py +296 -0
- mostlyright/data_harness/sources/catalog/contracts.py +825 -0
- mostlyright/data_harness/sources/catalog/coverage.py +137 -0
- mostlyright/data_harness/sources/catalog/delta.py +1340 -0
- mostlyright/data_harness/sources/catalog/embedding.py +532 -0
- mostlyright/data_harness/sources/catalog/entry_v2.py +1182 -0
- mostlyright/data_harness/sources/catalog/fill.py +3889 -0
- mostlyright/data_harness/sources/catalog/fill_partitions.py +459 -0
- mostlyright/data_harness/sources/catalog/fill_staging.py +1105 -0
- mostlyright/data_harness/sources/catalog/gating.py +374 -0
- mostlyright/data_harness/sources/catalog/generation_receipt.py +1607 -0
- mostlyright/data_harness/sources/catalog/harvest/__init__.py +7 -0
- mostlyright/data_harness/sources/catalog/harvest/ckan.py +384 -0
- mostlyright/data_harness/sources/catalog/harvest/datagov_v4.py +798 -0
- mostlyright/data_harness/sources/catalog/harvest/protocol.py +964 -0
- mostlyright/data_harness/sources/catalog/harvest/sdmx.py +445 -0
- mostlyright/data_harness/sources/catalog/harvest/stac.py +384 -0
- mostlyright/data_harness/sources/catalog/health.py +447 -0
- mostlyright/data_harness/sources/catalog/hosted_catalog.py +105 -0
- mostlyright/data_harness/sources/catalog/identity_history.py +1549 -0
- mostlyright/data_harness/sources/catalog/neural.py +1618 -0
- mostlyright/data_harness/sources/catalog/packed_catalog.py +2345 -0
- mostlyright/data_harness/sources/catalog/packed_retrieval.py +1517 -0
- mostlyright/data_harness/sources/catalog/packed_writer.py +2802 -0
- mostlyright/data_harness/sources/catalog/query_trace.py +1037 -0
- mostlyright/data_harness/sources/catalog/recommend.py +171 -0
- mostlyright/data_harness/sources/catalog/retrieval.py +230 -0
- mostlyright/data_harness/sources/catalog/retrieval_manifest.py +995 -0
- mostlyright/data_harness/sources/catalog/rights_decisions.py +254 -0
- mostlyright/data_harness/sources/catalog/sealed.py +560 -0
- mostlyright/data_harness/sources/catalog/search.py +230 -0
- mostlyright/data_harness/sources/catalog/streaming_delta.py +1097 -0
- mostlyright/data_harness/sources/catalog/update.py +891 -0
- mostlyright/data_harness/sources/collections.py +815 -0
- mostlyright/data_harness/sources/contracts.py +2223 -0
- mostlyright/data_harness/sources/deletion.py +761 -0
- mostlyright/data_harness/sources/fitness.py +162 -0
- mostlyright/data_harness/sources/governance.py +163 -0
- mostlyright/data_harness/sources/hosted.py +173 -0
- mostlyright/data_harness/sources/integration.py +218 -0
- mostlyright/data_harness/sources/range_reader.py +418 -0
- mostlyright/data_harness/sources/registry.py +514 -0
- mostlyright/data_harness/sources/rights_rule.py +59 -0
- mostlyright/data_harness/sources/source_cadence_vectors.v1.json +1 -0
- mostlyright/data_harness/sources/sports.py +521 -0
- mostlyright/data_harness/sources/stream.py +524 -0
- mostlyright/data_harness/sources/stream_connector.py +418 -0
- mostlyright/data_harness/sources/stream_recorder.py +1404 -0
- mostlyright/data_harness/studio_boundary.py +2019 -0
- mostlyright/data_harness/thin/__init__.py +37 -0
- mostlyright/data_harness/thin/acquire.py +1137 -0
- mostlyright/data_harness/thin/acquire_cancel.py +579 -0
- mostlyright/data_harness/thin/approvals.py +617 -0
- mostlyright/data_harness/thin/commands.py +406 -0
- mostlyright/data_harness/thin/download.py +194 -0
- mostlyright/data_harness/thin/narrative.py +589 -0
- mostlyright/data_harness/thin/parity.py +1070 -0
- mostlyright/data_harness/thin/propose.py +2759 -0
- mostlyright/data_harness/thin/research.py +1663 -0
- mostlyright/data_harness/thin/router.py +924 -0
- mostlyright/data_harness/thin/runs.py +519 -0
- mostlyright/data_harness/thin/session.py +281 -0
- mostlyright/data_harness/thin/stream.py +501 -0
- mostlyright/data_harness/thin/transport.py +187 -0
- mostlyright/data_harness/thin/vocabulary.py +368 -0
- mostlyright/data_harness/thin/workers.py +164 -0
- mostlyright/data_harness/ucum/TABLE-PIN.json +40 -0
- mostlyright/data_harness/ucum/ucum-subset.v1.json +632 -0
- mostlyright/data_harness/unit_flow.py +927 -0
- mostlyright/data_harness/units.py +572 -0
- mostlyright/data_harness/ux/__init__.py +9 -0
- mostlyright/data_harness/ux/approve.py +485 -0
- mostlyright/data_harness/ux/author_yaml.py +597 -0
- mostlyright/data_harness/ux/cloud_auth.py +447 -0
- mostlyright/data_harness/ux/commands/__init__.py +260 -0
- mostlyright/data_harness/ux/commands/approve.py +136 -0
- mostlyright/data_harness/ux/commands/auth.py +744 -0
- mostlyright/data_harness/ux/commands/author.py +79 -0
- mostlyright/data_harness/ux/commands/catalog_author.py +403 -0
- mostlyright/data_harness/ux/commands/catalog_fill.py +523 -0
- mostlyright/data_harness/ux/commands/catalog_harvest.py +545 -0
- mostlyright/data_harness/ux/commands/catalog_publish.py +1838 -0
- mostlyright/data_harness/ux/commands/catalog_search.py +71 -0
- mostlyright/data_harness/ux/commands/catalog_update.py +437 -0
- mostlyright/data_harness/ux/commands/deploy.py +134 -0
- mostlyright/data_harness/ux/commands/deploy_dataset.py +98 -0
- mostlyright/data_harness/ux/commands/deploy_plan.py +105 -0
- mostlyright/data_harness/ux/commands/deploy_status.py +104 -0
- mostlyright/data_harness/ux/commands/diff.py +74 -0
- mostlyright/data_harness/ux/commands/index.py +84 -0
- mostlyright/data_harness/ux/commands/inventory.py +47 -0
- mostlyright/data_harness/ux/commands/list_builds.py +143 -0
- mostlyright/data_harness/ux/commands/login.py +63 -0
- mostlyright/data_harness/ux/commands/peek.py +236 -0
- mostlyright/data_harness/ux/commands/plan_check.py +90 -0
- mostlyright/data_harness/ux/commands/preflight.py +97 -0
- mostlyright/data_harness/ux/commands/record.py +107 -0
- mostlyright/data_harness/ux/commands/review_setup.py +47 -0
- mostlyright/data_harness/ux/commands/search.py +440 -0
- mostlyright/data_harness/ux/commands/show.py +61 -0
- mostlyright/data_harness/ux/commands/whoami.py +37 -0
- mostlyright/data_harness/ux/credential_native.py +551 -0
- mostlyright/data_harness/ux/credential_store.py +1055 -0
- mostlyright/data_harness/ux/credentials.py +631 -0
- mostlyright/data_harness/ux/diffing.py +444 -0
- mostlyright/data_harness/ux/headline.py +671 -0
- mostlyright/data_harness/ux/hosted_acquisition.py +974 -0
- mostlyright/data_harness/ux/hosted_run_status.py +619 -0
- mostlyright/data_harness/ux/inventory.py +427 -0
- mostlyright/data_harness/ux/local_review.py +375 -0
- mostlyright/data_harness/ux/login.py +691 -0
- mostlyright/data_harness/ux/path_kind.py +147 -0
- mostlyright/data_harness/ux/peek.py +1000 -0
- mostlyright/data_harness/ux/plain_file.py +178 -0
- mostlyright/data_harness/ux/plan_check.py +311 -0
- mostlyright/data_harness/ux/preflight.py +918 -0
- mostlyright/data_harness/ux/readers.py +1124 -0
- mostlyright/data_harness/ux/remediation.py +2195 -0
- mostlyright/data_harness/ux/render.py +657 -0
- mostlyright/data_harness/ux/workload.py +1077 -0
- mostlyright/data_harness/viewer.py +3713 -0
- mostlyright/data_harness/visual_run/__init__.py +83 -0
- mostlyright/data_harness/visual_run/authoring.py +235 -0
- mostlyright/data_harness/visual_run/contracts.py +673 -0
- mostlyright/data_harness/visual_run/materialize.py +486 -0
- mostlyright/data_harness/visual_run/observations.py +874 -0
- mostlyright/data_harness/visual_run/query.py +259 -0
- mostlyright/data_harness/visual_run/reducer.py +280 -0
- mostlyright/data_harness/visual_run/sdk.py +892 -0
- mostlyright/data_harness/visual_run/store.py +584 -0
- mostlyright/data_harness/visual_run/transport.py +239 -0
- mostlyright/data_harness/watch.py +2999 -0
- mostlyright_data-0.9.0.dist-info/METADATA +607 -0
- mostlyright_data-0.9.0.dist-info/RECORD +314 -0
- mostlyright_data-0.9.0.dist-info/WHEEL +4 -0
- mostlyright_data-0.9.0.dist-info/entry_points.txt +12 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""Pluggable embedding backends, and the local integer encoder the catalog ships with.
|
|
2
|
+
|
|
3
|
+
Similarity finds candidates. Facts decide. Nothing in this module ever gates anything, and the
|
|
4
|
+
gating path never imports it.
|
|
5
|
+
|
|
6
|
+
Three properties are load-bearing.
|
|
7
|
+
|
|
8
|
+
**A backend is a seam, not a dependency.** ``EmbeddingBackend`` is a protocol and
|
|
9
|
+
``BackendRegistry`` is an allowlist in the same shape as the coordinator's adapter allowlist, so a
|
|
10
|
+
better encoder can be dropped in later. The default one, ``LexicalHashingBackend``, is pure
|
|
11
|
+
standard library: no network, no weights, no filesystem, no process spawn. A backend that cannot
|
|
12
|
+
say that about itself registers with ``query_safe=False`` and a catalog sealed by it refuses to
|
|
13
|
+
load at query time. ``DEFAULT_BACKENDS`` -- the allowlist the query-time load path falls back to --
|
|
14
|
+
is sealed at construction, so that fallback cannot be widened at run time by an importer; a
|
|
15
|
+
different inventory is a registry you build and pass, visibly, at the call site.
|
|
16
|
+
|
|
17
|
+
**No IEEE-754 arithmetic anywhere.** Every step from text to vector is integer: sha256 bucketing,
|
|
18
|
+
an integer signed accumulator, ``math.isqrt`` for the norm, and integer round-half-away-from-zero
|
|
19
|
+
for the scaling. That is not fastidiousness. A vector that differs in its last bit between arm64
|
|
20
|
+
and x86_64 would make retrieval host-dependent, and the digest authority refuses non-integer JSON
|
|
21
|
+
numbers outright, so a real number could never be sealed even if one were computed. A test reads
|
|
22
|
+
this module's own syntax tree and fails if a division, a square root, or the name of the
|
|
23
|
+
real-number type appears in it.
|
|
24
|
+
|
|
25
|
+
**One bounded seam carries provider-scale encoding.** ``encode_many`` is the only entry point a
|
|
26
|
+
provider-scale publisher may use, and it is a bounded loop over the same scalar ``encode`` rather
|
|
27
|
+
than a second encoder: at most 1000 items and 8 MiB of aggregate UTF-8 per invocation, one exact
|
|
28
|
+
vector per input in input order, all-or-nothing, and a counter record the seam mints from work it
|
|
29
|
+
measured itself. Nothing about it is faster than the scalar path, which is the honest position --
|
|
30
|
+
no native batch was measured on either declared runtime tuple, so none is offered and none is
|
|
31
|
+
claimed to be equivalent.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import hashlib
|
|
37
|
+
import math
|
|
38
|
+
import re
|
|
39
|
+
from dataclasses import dataclass
|
|
40
|
+
from typing import Any, Protocol
|
|
41
|
+
|
|
42
|
+
from mostlyright.data_harness.sources.catalog.contracts import QUANTIZATIONS
|
|
43
|
+
from mostlyright.data_harness.sources.contracts import SourceContractError
|
|
44
|
+
|
|
45
|
+
DIMENSIONS = 256
|
|
46
|
+
SCALE = 10_000
|
|
47
|
+
MAX_ENCODE_CHARS = 100_000
|
|
48
|
+
MIN_SUBTOKEN_SOURCE = 5
|
|
49
|
+
SUBTOKEN_LENGTH = 4
|
|
50
|
+
|
|
51
|
+
# The bounds on one outer `encode_many` call. Both are hard: a publisher that has more work than
|
|
52
|
+
# this issues more invocations, each one separately bounded and separately metered.
|
|
53
|
+
MAX_BATCH_ITEMS = 1_000
|
|
54
|
+
MAX_BATCH_UTF8_BYTES = 8 * 1024 * 1024
|
|
55
|
+
|
|
56
|
+
#: The only encoding-adapter mode that exists. It is a fixed property of this seam rather than a
|
|
57
|
+
#: caller-selectable option: every backend behind `encode_many` runs the same bounded scalar loop.
|
|
58
|
+
BOUNDED_SCALAR_ADAPTER = "bounded_scalar_adapter"
|
|
59
|
+
|
|
60
|
+
_TOKEN = re.compile(r"[a-z0-9]+")
|
|
61
|
+
_IDENTIFIER = re.compile(r"^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$")
|
|
62
|
+
_SEMVER = re.compile(r"^[1-9][0-9]*\.[0-9]+\.[0-9]+$")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _identifier(value: Any, path: str) -> str:
|
|
66
|
+
if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value):
|
|
67
|
+
raise SourceContractError(
|
|
68
|
+
"IDENTIFIER",
|
|
69
|
+
path,
|
|
70
|
+
"must be a lowercase dotted/dashed/underscored identifier",
|
|
71
|
+
)
|
|
72
|
+
return value
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _semver(value: Any, path: str) -> str:
|
|
76
|
+
if not isinstance(value, str) or not _SEMVER.fullmatch(value):
|
|
77
|
+
raise SourceContractError("SEMVER", path, "must be a positive-major semantic version")
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class BackendDescriptor:
|
|
83
|
+
"""The immutable identity of one embedding backend.
|
|
84
|
+
|
|
85
|
+
``query_safe`` is the query-time trust boundary. It is true only for a backend that is *pure*:
|
|
86
|
+
it opens no socket, reads no file, loads no weights, spawns no process, and calls no paid API.
|
|
87
|
+
A remote or weight-loading backend must register with ``query_safe=False``; a catalog sealed
|
|
88
|
+
by such a backend is then refused by the query-time load path rather than quietly pulling a
|
|
89
|
+
model onto the runtime path.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
backend_id: str
|
|
93
|
+
backend_version: str
|
|
94
|
+
dimensions: int
|
|
95
|
+
quantization: str
|
|
96
|
+
query_safe: bool
|
|
97
|
+
|
|
98
|
+
def __post_init__(self) -> None:
|
|
99
|
+
_identifier(self.backend_id, "backend.backend_id")
|
|
100
|
+
_semver(self.backend_version, "backend.backend_version")
|
|
101
|
+
if type(self.dimensions) is not int or not 1 <= self.dimensions <= 4_096:
|
|
102
|
+
raise SourceContractError(
|
|
103
|
+
"INTEGER",
|
|
104
|
+
"backend.dimensions",
|
|
105
|
+
"must be an integer in [1, 4096]",
|
|
106
|
+
)
|
|
107
|
+
if self.quantization not in QUANTIZATIONS:
|
|
108
|
+
raise SourceContractError(
|
|
109
|
+
"ENUM",
|
|
110
|
+
"backend.quantization",
|
|
111
|
+
f"must be one of {sorted(QUANTIZATIONS)}",
|
|
112
|
+
)
|
|
113
|
+
if type(self.query_safe) is not bool:
|
|
114
|
+
raise SourceContractError("BOOLEAN", "backend.query_safe", "must be a boolean")
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def coordinate(self) -> str:
|
|
118
|
+
return f"{self.backend_id}@{self.backend_version}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(frozen=True)
|
|
122
|
+
class PinnedLocalBackendDescriptor(BackendDescriptor):
|
|
123
|
+
"""Explicit v2 identity for a verified local-model sidecar.
|
|
124
|
+
|
|
125
|
+
This is deliberately not a reinterpretation of ``query_safe``. A local model reads governed
|
|
126
|
+
sidecar bytes and loads an optional runtime, so it remains false and cannot enter a v1 sealed
|
|
127
|
+
index. The v2 caller must select the resolver explicitly and receives the root, PIN, artifact,
|
|
128
|
+
and supported runtime-tuple coordinate it selected.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
model_root: str
|
|
132
|
+
pin_sha256: str
|
|
133
|
+
coordinate_sha256: str
|
|
134
|
+
runtime_tuple: str
|
|
135
|
+
runtime_closure_sha256: str
|
|
136
|
+
artifact_sha256s: tuple[tuple[str, str], ...]
|
|
137
|
+
|
|
138
|
+
def __post_init__(self) -> None:
|
|
139
|
+
super().__post_init__()
|
|
140
|
+
if self.query_safe:
|
|
141
|
+
raise SourceContractError(
|
|
142
|
+
"NEURAL_QUERY_SAFE",
|
|
143
|
+
"backend.query_safe",
|
|
144
|
+
"a sidecar-loading backend cannot claim the v1 query-safe capability",
|
|
145
|
+
)
|
|
146
|
+
if not isinstance(self.model_root, str) or not self.model_root.startswith("/"):
|
|
147
|
+
raise SourceContractError(
|
|
148
|
+
"NEURAL_MODEL_ROOT_ABSOLUTE",
|
|
149
|
+
"backend.model_root",
|
|
150
|
+
"must be an absolute caller-supplied path",
|
|
151
|
+
)
|
|
152
|
+
for value, path in (
|
|
153
|
+
(self.pin_sha256, "backend.pin_sha256"),
|
|
154
|
+
(self.coordinate_sha256, "backend.coordinate_sha256"),
|
|
155
|
+
(self.runtime_closure_sha256, "backend.runtime_closure_sha256"),
|
|
156
|
+
):
|
|
157
|
+
if not isinstance(value, str) or re.fullmatch(r"[0-9a-f]{64}", value) is None:
|
|
158
|
+
raise SourceContractError("SHA256", path, "must be a lowercase SHA-256 digest")
|
|
159
|
+
_identifier(self.runtime_tuple, "backend.runtime_tuple")
|
|
160
|
+
if (
|
|
161
|
+
not self.artifact_sha256s
|
|
162
|
+
or tuple(sorted(self.artifact_sha256s)) != self.artifact_sha256s
|
|
163
|
+
):
|
|
164
|
+
raise SourceContractError(
|
|
165
|
+
"NEURAL_ARTIFACT_COORDINATE",
|
|
166
|
+
"backend.artifact_sha256s",
|
|
167
|
+
"must be a non-empty path-sorted artifact coordinate",
|
|
168
|
+
)
|
|
169
|
+
for path, digest in self.artifact_sha256s:
|
|
170
|
+
if not isinstance(path, str) or not path or "/" in path or "\\" in path:
|
|
171
|
+
raise SourceContractError(
|
|
172
|
+
"NEURAL_ARTIFACT_COORDINATE",
|
|
173
|
+
"backend.artifact_sha256s",
|
|
174
|
+
"artifact paths must be single safe names",
|
|
175
|
+
)
|
|
176
|
+
if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None:
|
|
177
|
+
raise SourceContractError(
|
|
178
|
+
"NEURAL_ARTIFACT_COORDINATE",
|
|
179
|
+
"backend.artifact_sha256s",
|
|
180
|
+
"artifact digests must be lowercase SHA-256 values",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def coordinate(self) -> str:
|
|
185
|
+
return (
|
|
186
|
+
f"{self.backend_id}@{self.backend_version}"
|
|
187
|
+
f"#{self.runtime_tuple}:{self.coordinate_sha256}:{self.runtime_closure_sha256}"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass(frozen=True)
|
|
192
|
+
class BatchEncodeResult:
|
|
193
|
+
"""One outer bounded encoding invocation: its vectors, and the work it metered doing them.
|
|
194
|
+
|
|
195
|
+
The counters are the point. A provider-scale publisher has to state how much encoding a
|
|
196
|
+
generation cost, and a total it computes from its own intentions proves nothing -- it would
|
|
197
|
+
reconcile with itself whether or not the encoder ran. So this record is minted by
|
|
198
|
+
``encode_many`` from work it observed: ``batch_invocations`` is always exactly one because this
|
|
199
|
+
record *is* one invocation, ``scalar_invocations`` is the number of times the underlying scalar
|
|
200
|
+
``encode`` was actually called, and ``encoded_utf8_bytes`` is the aggregate the seam measured
|
|
201
|
+
before it encoded anything. A publisher spanning several invocations sums ordered records; it
|
|
202
|
+
never authors one.
|
|
203
|
+
|
|
204
|
+
``adapter_mode`` is fixed. It exists so the publication evidence says out loud which
|
|
205
|
+
implementation produced the vectors, and there is exactly one: the bounded scalar adapter. No
|
|
206
|
+
native batching was measured on either declared runtime tuple, so no other mode may be recorded
|
|
207
|
+
here, and a record claiming one is refused rather than believed.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
adapter_mode: str
|
|
211
|
+
backend_coordinate: str
|
|
212
|
+
vectors: tuple[tuple[int, ...], ...]
|
|
213
|
+
batch_invocations: int
|
|
214
|
+
scalar_invocations: int
|
|
215
|
+
encoded_utf8_bytes: int
|
|
216
|
+
|
|
217
|
+
def __post_init__(self) -> None:
|
|
218
|
+
if self.adapter_mode != BOUNDED_SCALAR_ADAPTER:
|
|
219
|
+
raise SourceContractError(
|
|
220
|
+
"EMBEDDING_BATCH_ADAPTER",
|
|
221
|
+
"embedding.batch.adapter_mode",
|
|
222
|
+
f"the only encoding adapter is {BOUNDED_SCALAR_ADAPTER!r}",
|
|
223
|
+
)
|
|
224
|
+
if not isinstance(self.backend_coordinate, str) or not self.backend_coordinate:
|
|
225
|
+
raise SourceContractError(
|
|
226
|
+
"EMBEDDING_BATCH_COUNTER",
|
|
227
|
+
"embedding.batch.backend_coordinate",
|
|
228
|
+
"the invocation must name the exact backend coordinate that produced it",
|
|
229
|
+
)
|
|
230
|
+
if type(self.vectors) is not tuple or not 1 <= len(self.vectors) <= MAX_BATCH_ITEMS:
|
|
231
|
+
raise SourceContractError(
|
|
232
|
+
"EMBEDDING_BATCH_COUNTER",
|
|
233
|
+
"embedding.batch.vectors",
|
|
234
|
+
f"one invocation carries 1 to {MAX_BATCH_ITEMS} vectors",
|
|
235
|
+
)
|
|
236
|
+
if type(self.batch_invocations) is not int or self.batch_invocations != 1:
|
|
237
|
+
raise SourceContractError(
|
|
238
|
+
"EMBEDDING_BATCH_COUNTER",
|
|
239
|
+
"embedding.batch.batch_invocations",
|
|
240
|
+
"one counter record accounts for exactly one outer batch invocation",
|
|
241
|
+
)
|
|
242
|
+
if type(self.scalar_invocations) is not int or self.scalar_invocations != len(self.vectors):
|
|
243
|
+
raise SourceContractError(
|
|
244
|
+
"EMBEDDING_BATCH_COUNTER",
|
|
245
|
+
"embedding.batch.scalar_invocations",
|
|
246
|
+
"the bounded scalar adapter invokes scalar encode exactly once per item",
|
|
247
|
+
)
|
|
248
|
+
if (
|
|
249
|
+
type(self.encoded_utf8_bytes) is not int
|
|
250
|
+
or not 0 <= self.encoded_utf8_bytes <= MAX_BATCH_UTF8_BYTES
|
|
251
|
+
):
|
|
252
|
+
raise SourceContractError(
|
|
253
|
+
"EMBEDDING_BATCH_COUNTER",
|
|
254
|
+
"embedding.batch.encoded_utf8_bytes",
|
|
255
|
+
f"metered input bytes must be in [0, {MAX_BATCH_UTF8_BYTES}]",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class EmbeddingBackend(Protocol):
|
|
260
|
+
"""One way of turning text into a sealed integer vector."""
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def descriptor(self) -> BackendDescriptor:
|
|
264
|
+
"""Return the immutable backend identity."""
|
|
265
|
+
|
|
266
|
+
def encode(self, text: str) -> tuple[int, ...]:
|
|
267
|
+
"""Return this text's quantized vector, of exactly ``descriptor.dimensions`` integers."""
|
|
268
|
+
|
|
269
|
+
def encode_many(self, texts: tuple[str, ...] | list[str]) -> BatchEncodeResult:
|
|
270
|
+
"""Return one exact vector per input, in order, with this invocation's own counters."""
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def encode_many_bounded(
|
|
274
|
+
backend: EmbeddingBackend, texts: tuple[str, ...] | list[str]
|
|
275
|
+
) -> BatchEncodeResult:
|
|
276
|
+
"""The one bounded provider-scale seam: a scalar loop, in order, with metered work.
|
|
277
|
+
|
|
278
|
+
Every backend's ``encode_many`` is this function. That is deliberate. A per-backend batch
|
|
279
|
+
implementation is where a batch stops being equal to its scalar equivalent -- padding to the
|
|
280
|
+
longest member of a batch, a different reduction order, a runtime kernel selected by shape --
|
|
281
|
+
and no cross-platform receipt proves that any of those still produce the sealed bytes.
|
|
282
|
+
A loop needs no such receipt: the vectors are the scalar vectors, because they *are* the scalar
|
|
283
|
+
vectors.
|
|
284
|
+
|
|
285
|
+
Two things are metered separately and neither is inferred from the other. Aggregate UTF-8 bytes
|
|
286
|
+
are counted first, over the whole input, before a single item is encoded, so an oversized batch
|
|
287
|
+
costs nothing to refuse. Underlying scalar invocations are then counted as they happen, so the
|
|
288
|
+
reported count is an observation rather than ``len(texts)`` restated.
|
|
289
|
+
|
|
290
|
+
The seam is all-or-nothing. A typed refusal from the scalar encoder -- a deadline, a lost
|
|
291
|
+
worker, an over-long item -- propagates unchanged and no partial batch is returned, because a
|
|
292
|
+
publisher that received nine of ten vectors would have no honest way to say so.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
if type(texts) is not tuple and type(texts) is not list:
|
|
296
|
+
raise SourceContractError(
|
|
297
|
+
"EMBEDDING_BATCH_SEQUENCE",
|
|
298
|
+
"embedding.texts",
|
|
299
|
+
"must be a materialized tuple or list of texts, not a string, mapping, or iterator",
|
|
300
|
+
)
|
|
301
|
+
items = tuple(texts)
|
|
302
|
+
if not items:
|
|
303
|
+
raise SourceContractError(
|
|
304
|
+
"EMBEDDING_BATCH_EMPTY",
|
|
305
|
+
"embedding.texts",
|
|
306
|
+
"one invocation encodes at least one text",
|
|
307
|
+
)
|
|
308
|
+
if len(items) > MAX_BATCH_ITEMS:
|
|
309
|
+
raise SourceContractError(
|
|
310
|
+
"EMBEDDING_BATCH_ITEM_LIMIT",
|
|
311
|
+
"embedding.texts",
|
|
312
|
+
f"one invocation encodes at most {MAX_BATCH_ITEMS} texts",
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
encoded_utf8_bytes = 0
|
|
316
|
+
for position, text in enumerate(items):
|
|
317
|
+
if type(text) is not str:
|
|
318
|
+
raise SourceContractError(
|
|
319
|
+
"EMBEDDING_BATCH_ITEM_TYPE",
|
|
320
|
+
f"embedding.texts[{position}]",
|
|
321
|
+
"every item must be a string",
|
|
322
|
+
)
|
|
323
|
+
encoded_utf8_bytes += len(text.encode("utf-8"))
|
|
324
|
+
if encoded_utf8_bytes > MAX_BATCH_UTF8_BYTES:
|
|
325
|
+
raise SourceContractError(
|
|
326
|
+
"EMBEDDING_BATCH_BYTE_LIMIT",
|
|
327
|
+
"embedding.texts",
|
|
328
|
+
f"one invocation encodes at most {MAX_BATCH_UTF8_BYTES} aggregate UTF-8 bytes",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
dimensions = backend.descriptor.dimensions
|
|
332
|
+
vectors: list[tuple[int, ...]] = []
|
|
333
|
+
first_seen: dict[str, tuple[int, ...]] = {}
|
|
334
|
+
scalar_invocations = 0
|
|
335
|
+
for position, text in enumerate(items):
|
|
336
|
+
vector = backend.encode(text)
|
|
337
|
+
scalar_invocations += 1
|
|
338
|
+
if (
|
|
339
|
+
type(vector) is not tuple
|
|
340
|
+
or len(vector) != dimensions
|
|
341
|
+
or any(type(value) is not int for value in vector)
|
|
342
|
+
):
|
|
343
|
+
raise SourceContractError(
|
|
344
|
+
"EMBEDDING_BATCH_RESULT",
|
|
345
|
+
f"embedding.vectors[{position}]",
|
|
346
|
+
f"each scalar result must be exactly {dimensions} integers",
|
|
347
|
+
)
|
|
348
|
+
# A repeated text must receive its own identical vector. The encoder is deterministic, so
|
|
349
|
+
# a disagreement here is a result arriving against the wrong request -- a reordered or
|
|
350
|
+
# stale response -- which is the one mis-binding a serial loop cannot otherwise see.
|
|
351
|
+
if first_seen.setdefault(text, vector) != vector:
|
|
352
|
+
raise SourceContractError(
|
|
353
|
+
"EMBEDDING_BATCH_ORDER",
|
|
354
|
+
f"embedding.vectors[{position}]",
|
|
355
|
+
"a repeated text received a different vector, so results are not in input order",
|
|
356
|
+
)
|
|
357
|
+
vectors.append(vector)
|
|
358
|
+
|
|
359
|
+
if len(vectors) != len(items) or scalar_invocations != len(items):
|
|
360
|
+
raise SourceContractError(
|
|
361
|
+
"EMBEDDING_BATCH_RESULT",
|
|
362
|
+
"embedding.vectors",
|
|
363
|
+
"the invocation produced a partial result set",
|
|
364
|
+
)
|
|
365
|
+
return BatchEncodeResult(
|
|
366
|
+
adapter_mode=BOUNDED_SCALAR_ADAPTER,
|
|
367
|
+
backend_coordinate=backend.descriptor.coordinate,
|
|
368
|
+
vectors=tuple(vectors),
|
|
369
|
+
batch_invocations=1,
|
|
370
|
+
scalar_invocations=scalar_invocations,
|
|
371
|
+
encoded_utf8_bytes=encoded_utf8_bytes,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def resolve_local_neural_backend(
|
|
376
|
+
model_root: Any,
|
|
377
|
+
) -> EmbeddingBackend:
|
|
378
|
+
"""Resolve the governed v2 neural backend from one explicit sidecar root.
|
|
379
|
+
|
|
380
|
+
The exact module name is the allowlisted boundary. A built-in dynamic import keeps this module
|
|
381
|
+
and all lexical/query imports free of the optional runtime. Resolution prepares the
|
|
382
|
+
authenticated private worker before returning, so each later ``encode`` call covers inference
|
|
383
|
+
only.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
module = __import__(
|
|
387
|
+
"mostlyright.data_harness.sources.catalog.neural",
|
|
388
|
+
fromlist=("resolve_local_neural_backend",),
|
|
389
|
+
)
|
|
390
|
+
return module.resolve_local_neural_backend(model_root)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class BackendRegistry:
|
|
394
|
+
"""Allowlist of embedding backends, in the shape of the coordinator's adapter allowlist.
|
|
395
|
+
|
|
396
|
+
``sealed`` closes the allowlist to further registration. It exists for one instance --
|
|
397
|
+
``DEFAULT_BACKENDS``, the registry ``SealedEmbeddingIndex.load`` resolves against when a caller
|
|
398
|
+
names none. A process-global whose membership any
|
|
399
|
+
importer can widen at run time is not a control: widening it with a ``query_safe=True``
|
|
400
|
+
descriptor is all it would take to make ``load`` accept a catalog sealed by a backend that
|
|
401
|
+
opens a socket. A caller that genuinely wants a different inventory builds its own registry and
|
|
402
|
+
passes it, which is a visible act at the call site rather than an invisible one at import time.
|
|
403
|
+
|
|
404
|
+
Sealing is one-way and set at construction. There is no unseal.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
def __init__(
|
|
408
|
+
self,
|
|
409
|
+
backends: tuple[EmbeddingBackend, ...] = (),
|
|
410
|
+
*,
|
|
411
|
+
sealed: bool = False,
|
|
412
|
+
) -> None:
|
|
413
|
+
self._backends: dict[tuple[str, str], EmbeddingBackend] = {}
|
|
414
|
+
self._sealed = False
|
|
415
|
+
for backend in backends:
|
|
416
|
+
self.register(backend)
|
|
417
|
+
if type(sealed) is not bool:
|
|
418
|
+
raise SourceContractError("BOOLEAN", "registry.sealed", "must be a boolean")
|
|
419
|
+
self._sealed = sealed
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def sealed(self) -> bool:
|
|
423
|
+
"""Whether this allowlist is closed to further registration."""
|
|
424
|
+
|
|
425
|
+
return self._sealed
|
|
426
|
+
|
|
427
|
+
def register(self, backend: EmbeddingBackend) -> None:
|
|
428
|
+
if self._sealed:
|
|
429
|
+
raise SourceContractError(
|
|
430
|
+
"EMBEDDING_BACKEND_REGISTRY_SEALED",
|
|
431
|
+
"registry",
|
|
432
|
+
"this backend allowlist is sealed and cannot be widened; build your own registry "
|
|
433
|
+
"and pass it explicitly instead",
|
|
434
|
+
)
|
|
435
|
+
descriptor = backend.descriptor
|
|
436
|
+
if not isinstance(descriptor, BackendDescriptor):
|
|
437
|
+
raise SourceContractError(
|
|
438
|
+
"EMBEDDING_BACKEND_DESCRIPTOR",
|
|
439
|
+
"backend.descriptor",
|
|
440
|
+
"backend descriptor must use the strict contract",
|
|
441
|
+
)
|
|
442
|
+
key = (descriptor.backend_id, descriptor.backend_version)
|
|
443
|
+
if key in self._backends:
|
|
444
|
+
raise SourceContractError(
|
|
445
|
+
"EMBEDDING_BACKEND_DUPLICATE",
|
|
446
|
+
"registry",
|
|
447
|
+
f"backend {descriptor.coordinate} is already registered",
|
|
448
|
+
)
|
|
449
|
+
self._backends[key] = backend
|
|
450
|
+
|
|
451
|
+
def resolve(self, backend_id: str, backend_version: str) -> EmbeddingBackend:
|
|
452
|
+
try:
|
|
453
|
+
return self._backends[(backend_id, backend_version)]
|
|
454
|
+
except KeyError:
|
|
455
|
+
raise SourceContractError(
|
|
456
|
+
"EMBEDDING_BACKEND_UNAVAILABLE",
|
|
457
|
+
"vector.backend_id",
|
|
458
|
+
f"backend {backend_id}@{backend_version} is not allowlisted",
|
|
459
|
+
) from None
|
|
460
|
+
|
|
461
|
+
def inventory(self) -> tuple[BackendDescriptor, ...]:
|
|
462
|
+
return tuple(self._backends[key].descriptor for key in sorted(self._backends))
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class LexicalHashingBackend:
|
|
466
|
+
"""The default local encoder: hashed unigrams plus character 4-grams, integer-quantized.
|
|
467
|
+
|
|
468
|
+
The 4-grams are a cheap stemming surrogate. They are what lets a query phrased as "produce"
|
|
469
|
+
reach a description that says "production" without a stemmer, a stopword list, or a model.
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
_DESCRIPTOR = BackendDescriptor(
|
|
473
|
+
backend_id="lexical_hashing",
|
|
474
|
+
backend_version="1.0.0",
|
|
475
|
+
dimensions=DIMENSIONS,
|
|
476
|
+
quantization="int32_fixed_point_10000",
|
|
477
|
+
query_safe=True,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
@property
|
|
481
|
+
def descriptor(self) -> BackendDescriptor:
|
|
482
|
+
return self._DESCRIPTOR
|
|
483
|
+
|
|
484
|
+
def encode(self, text: str) -> tuple[int, ...]:
|
|
485
|
+
if not isinstance(text, str):
|
|
486
|
+
raise SourceContractError("TYPE", "embedding.text", "must be a string")
|
|
487
|
+
if len(text) > MAX_ENCODE_CHARS:
|
|
488
|
+
raise SourceContractError(
|
|
489
|
+
"EMBEDDING_INPUT_LIMIT",
|
|
490
|
+
"embedding.text",
|
|
491
|
+
f"encode input exceeds {MAX_ENCODE_CHARS} characters",
|
|
492
|
+
)
|
|
493
|
+
accumulator = [0] * DIMENSIONS
|
|
494
|
+
for token in _tokens(text):
|
|
495
|
+
digest = hashlib.sha256(token.encode("utf-8")).digest()
|
|
496
|
+
bucket = int.from_bytes(digest[:4], "big") % DIMENSIONS
|
|
497
|
+
accumulator[bucket] += 1 if digest[4] & 1 else -1
|
|
498
|
+
square_sum = sum(value * value for value in accumulator)
|
|
499
|
+
if square_sum == 0:
|
|
500
|
+
return (0,) * DIMENSIONS
|
|
501
|
+
# Exact integer square root. No IEEE-754 operation is involved anywhere on this path, so
|
|
502
|
+
# the vector is bit-identical on every host.
|
|
503
|
+
norm = math.isqrt(square_sum)
|
|
504
|
+
if norm == 0:
|
|
505
|
+
return (0,) * DIMENSIONS
|
|
506
|
+
scaled: list[int] = []
|
|
507
|
+
for value in accumulator:
|
|
508
|
+
sign = -1 if value < 0 else 1
|
|
509
|
+
# Integer round-half-away-from-zero of |value| * SCALE / norm.
|
|
510
|
+
scaled.append(sign * ((abs(value) * SCALE * 2 + norm) // (norm * 2)))
|
|
511
|
+
return tuple(scaled)
|
|
512
|
+
|
|
513
|
+
def encode_many(self, texts: tuple[str, ...] | list[str]) -> BatchEncodeResult:
|
|
514
|
+
return encode_many_bounded(self, texts)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _tokens(text: str) -> list[str]:
|
|
518
|
+
"""Lowercased unigrams, plus character 4-grams of every unigram longer than five."""
|
|
519
|
+
|
|
520
|
+
tokens: list[str] = []
|
|
521
|
+
for match in _TOKEN.finditer(text.lower()):
|
|
522
|
+
token = match.group()
|
|
523
|
+
tokens.append(token)
|
|
524
|
+
if len(token) > MIN_SUBTOKEN_SOURCE:
|
|
525
|
+
for start in range(len(token) - SUBTOKEN_LENGTH + 1):
|
|
526
|
+
tokens.append(token[start : start + SUBTOKEN_LENGTH])
|
|
527
|
+
return tokens
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
# Sealed. This is the allowlist `SealedEmbeddingIndex.load` uses when a caller names none.
|
|
531
|
+
# See `BackendRegistry` for why it may not be widened in place.
|
|
532
|
+
DEFAULT_BACKENDS = BackendRegistry((LexicalHashingBackend(),), sealed=True)
|