diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90f0aa5..eadd649 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,8 +37,8 @@ jobs: - run: pip install build - run: | - rm -r src/synapt_extract/prompts 2>/dev/null || true - test -d ../../prompts && cp -r ../../prompts src/synapt_extract/prompts || true + rm -r src/synapt/extract/prompts 2>/dev/null || true + test -d ../../prompts && cp -r ../../prompts src/synapt/extract/prompts || true - run: python -m build @@ -154,7 +154,7 @@ jobs: - name: Verify Python package schemas match root schemas run: | - diff -r schemas packages/python/src/synapt_extract/schemas + diff -r schemas packages/python/src/synapt/extract/schemas echo "✅ Python package schemas match root schemas" schema-url-check: @@ -205,8 +205,8 @@ jobs: SOURCE_DATE_EPOCH: "1704067200" run: | pip install build - rm -r src/synapt_extract/prompts 2>/dev/null || true - test -d ../../prompts && cp -r ../../prompts src/synapt_extract/prompts || true + rm -r src/synapt/extract/prompts 2>/dev/null || true + test -d ../../prompts && cp -r ../../prompts src/synapt/extract/prompts || true python -m build --outdir /tmp/py-dist-1 rm -r src/synapt_extract.egg-info build 2>/dev/null || true python -m build --outdir /tmp/py-dist-2 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 82e4fae..6660f60 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -25,8 +25,8 @@ jobs: - run: pip install build - run: | - rm -r src/synapt_extract/prompts 2>/dev/null || true - test -d ../../prompts && cp -r ../../prompts src/synapt_extract/prompts || true + rm -r src/synapt/extract/prompts 2>/dev/null || true + test -d ../../prompts && cp -r ../../prompts src/synapt/extract/prompts || true - run: python -m build diff --git a/.gitignore b/.gitignore index 6ff5241..9095198 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ dist/ !tests/security-probes/**/*.js packages/ts/prompts/ -packages/python/src/synapt_extract/prompts/ +packages/python/src/synapt/extract/prompts/ __pycache__/ *.pyc diff --git a/README.md b/README.md index eb88c00..b8a6e4d 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ console.log(result.validation); // { valid: true, errors: [] } ### Python ```python -from synapt_extract import ( +from synapt.extract import ( build_extraction_prompt, finalize_extraction, FinalizeContext, @@ -124,7 +124,7 @@ const built = builder.build({ name: "synapt_extract_stage1" }); ### Python ```python -from synapt_extract import create_extraction_builder +from synapt.extract import create_extraction_builder builder = ( create_extraction_builder(text, profile="standard") @@ -173,7 +173,7 @@ const result = await extractOpenAI(text, new OpenAI(), { ```python from openai import OpenAI -from synapt_extract import create_extraction_builder, extract_openai +from synapt.extract import create_extraction_builder, extract_openai builder = ( create_extraction_builder(text) @@ -192,7 +192,7 @@ result = await extract_openai( ) ``` -The returned result includes `artifactBundle` / `artifact_bundle`. TypeScript exports the Node artifact writer at `@synapt-dev/extract/artifacts`; Python exports `write_artifact_bundle()` from `synapt_extract`. +The returned result includes `artifactBundle` / `artifact_bundle`. TypeScript exports the Node artifact writer at `@synapt-dev/extract/artifacts`; Python exports `write_artifact_bundle()` from `synapt.extract`. ```typescript import { createExtractionBuilder, extract } from "@synapt-dev/extract"; @@ -227,7 +227,7 @@ const result = await extract(text, { ``` ```python -from synapt_extract import create_extraction_builder, extract +from synapt.extract import create_extraction_builder, extract builder = ( create_extraction_builder(text) diff --git a/SECURITY.md b/SECURITY.md index 50d123d..2ce36d6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -78,8 +78,8 @@ sha256sum *.tgz # Python (wheel) cd packages/python -cp -r ../../prompts src/synapt_extract/prompts -cp -r ../../schemas src/synapt_extract/schemas +cp -r ../../prompts src/synapt/extract/prompts +cp -r ../../schemas src/synapt/extract/schemas SOURCE_DATE_EPOCH=1704067200 python -m build sha256sum dist/*.whl ``` diff --git a/examples/dogfood.py b/examples/dogfood.py index 35c445c..f2d3b47 100644 --- a/examples/dogfood.py +++ b/examples/dogfood.py @@ -12,9 +12,9 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "packages" / "python" / "src")) import anthropic -from synapt_extract.prompt import build_extraction_prompt -from synapt_extract.finalize import FinalizeContext, finalize_extraction -from synapt_extract.validate import validate_extraction +from synapt.extract.prompt import build_extraction_prompt +from synapt.extract.finalize import FinalizeContext, finalize_extraction +from synapt.extract.validate import validate_extraction CONVERSATION = """\ Session: Weekly check-in with Marcus, April 22, 2026 diff --git a/packages/python/README.md b/packages/python/README.md index cb19dc0..d2709f3 100644 --- a/packages/python/README.md +++ b/packages/python/README.md @@ -15,7 +15,7 @@ pip install synapt-extract ## Quick start ```python -from synapt_extract import ( +from synapt.extract import ( build_extraction_prompt, finalize_extraction, FinalizeContext, @@ -50,7 +50,7 @@ assert result.validation.valid Use the builder when the model API supports structured output. It resolves capabilities once, then builds the matching prompt, Stage 1 JSON schema, OpenAI response format, finalized packet schema, and optional finalization context. ```python -from synapt_extract import create_extraction_builder +from synapt.extract import create_extraction_builder builder = ( create_extraction_builder(text, profile="standard") @@ -81,7 +81,7 @@ For OpenAI-compatible clients, use the thin adapter instead of writing callbacks ```python from openai import OpenAI -from synapt_extract import create_extraction_builder, extract_openai +from synapt.extract import create_extraction_builder, extract_openai builder = ( create_extraction_builder(text) @@ -103,7 +103,7 @@ result = await extract_openai( The returned result includes `artifact_bundle`. `write_artifact_bundle()` can also write a bundle created from any `extract()` result. ```python -from synapt_extract import create_extraction_builder, extract +from synapt.extract import create_extraction_builder, extract builder = ( create_extraction_builder(text) diff --git a/packages/python/pyproject.toml b/packages/python/pyproject.toml index 68a0560..5f899c3 100644 --- a/packages/python/pyproject.toml +++ b/packages/python/pyproject.toml @@ -32,6 +32,7 @@ Schema = "https://synapt.dev/schemas/extract/v1.json" [tool.setuptools.packages.find] where = ["src"] +namespaces = true [tool.setuptools.package-data] -synapt_extract = ["prompts/**/*.txt", "prompts/**/*.json", "schemas/**/*.json"] +"synapt.extract" = ["prompts/**/*.txt", "prompts/**/*.json", "schemas/**/*.json"] diff --git a/packages/python/src/synapt_extract/__init__.py b/packages/python/src/synapt/extract/__init__.py similarity index 80% rename from packages/python/src/synapt_extract/__init__.py rename to packages/python/src/synapt/extract/__init__.py index c52dc01..1394933 100644 --- a/packages/python/src/synapt_extract/__init__.py +++ b/packages/python/src/synapt/extract/__init__.py @@ -1,6 +1,6 @@ """synapt-extract: SynaptExtraction IL v1 schema, validation, and finalization.""" -from synapt_extract.schema import ( +from synapt.extract.schema import ( SynaptExtraction, SynaptEntity, SynaptGoal, @@ -16,9 +16,9 @@ SynaptAssertionSignals, SynaptTemporalRef, ) -from synapt_extract.validate import validate_extraction, ValidationResult, ValidationError -from synapt_extract.finalize import finalize_extraction, FinalizeContext, FinalizeResult -from synapt_extract.prompt import ( +from synapt.extract.validate import validate_extraction, ValidationResult, ValidationError +from synapt.extract.finalize import finalize_extraction, FinalizeContext, FinalizeResult +from synapt.extract.prompt import ( build_extraction_prompt, capability_embedding_input, profile_capabilities, @@ -27,14 +27,14 @@ CAPABILITY_REGISTRY, STANDARD_EMBEDDING_INPUTS, ) -from synapt_extract.builder import ( +from synapt.extract.builder import ( ExtractionBuilder, build_finalized_extraction_schema, build_extraction_schema, build_extraction_response_format, create_extraction_builder, ) -from synapt_extract.extract import ( +from synapt.extract.extract import ( extract, normalize_llm_response, run_extraction, @@ -52,15 +52,22 @@ NormalizedLlmResponse, UsageSummary, ) -from synapt_extract.artifacts import ( +from synapt.extract.artifacts import ( create_artifact_bundle, sha256_text, write_artifact_bundle, ) -from synapt_extract.openai import ( +from synapt.extract.openai import ( extract_openai, OpenAIExtractResult, ) +from synapt.extract.batch import ( + BatchFailureReason, + BatchInferRequest, + BatchUnit, + BatchUnitResult, + extract_batch, +) __all__ = [ "SynaptExtraction", @@ -116,4 +123,9 @@ "write_artifact_bundle", "extract_openai", "OpenAIExtractResult", + "BatchFailureReason", + "BatchInferRequest", + "BatchUnit", + "BatchUnitResult", + "extract_batch", ] diff --git a/packages/python/src/synapt_extract/artifacts.py b/packages/python/src/synapt/extract/artifacts.py similarity index 98% rename from packages/python/src/synapt_extract/artifacts.py rename to packages/python/src/synapt/extract/artifacts.py index dc8c3db..12e4603 100644 --- a/packages/python/src/synapt_extract/artifacts.py +++ b/packages/python/src/synapt/extract/artifacts.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import Any -from synapt_extract.extract import ExtractResult +from synapt.extract.extract import ExtractResult JsonObject = dict[str, Any] diff --git a/packages/python/src/synapt/extract/batch.py b/packages/python/src/synapt/extract/batch.py new file mode 100644 index 0000000..5197565 --- /dev/null +++ b/packages/python/src/synapt/extract/batch.py @@ -0,0 +1,130 @@ +"""Batch Stage-1 extraction primitive for SynaptExtraction. + +SKELETON (recall#868 → extract_batch). API conformed to the pinned contract +(config/design/extract-batch-limits-characterization-2026-07-13.md §"Contract +decisions") AND to Sentinel's spec (extract#28, tests/python/test_extract_batch.py). +Every body raises NotImplementedError — the implementation lands in the follow-up +impl PR (TDD: this skeleton makes the spec COLLECT and run RED, not ImportError). + +Why this primitive exists +------------------------- +Atlas's characterization found the generic single-text builder cannot reliably +produce a schema-valid packet even for ONE clean pre-identified unit (NO_VIABLE_N +at N=1). The failure is MALFORMATION on GROUNDED content (40/40 source-supported), +not confabulation — the model returns the right facts in the wrong shape. Fixed +with structural machinery (shaping + per-item validation + fallback), not prompt +tuning. A NEW primitive, not a wrapper/loop over the generic builder. + +Contract (pinned + spec-confirmed) +---------------------------------- + • Input: list[BatchUnit(id, text, capabilities?)] — explicit attribution; the + id rides into the output as source_unit_id (boundaries stay out-of-band, never + in model-visible text). + • Inference: an injected `infer` seam receiving a request {prompt, messages, + capabilities} and returning a completion string. ZERO recall dependency. + • v1 strategy: PER-UNIT (one infer call per unit) — trivially out-of-band, clean + 1:1 attribution. batch-all / safe-N are future INTERNAL ladder rungs, not v1 + contract surface. Retry: one deterministic retry per failed unit (2 attempts + total), then a terminal marker from the last failure class; never replay a + successful neighbor. + • Output: COUNT-INVARIANT len(out)==len(in). Each unit → an "ok" BatchUnitResult + (valid envelope) OR a terminal {source_unit_id, status:"failed", reason} marker. + reason ∈ BatchFailureReason. No silent drops. + • Shaping (folded from recall #870/#871, held/superseded): + Class-A PRE-parse text hygiene — strip ``` fences + `//` comments, STRING- + LITERAL-AWARE (a `//` inside a JSON string, e.g. a URL, must survive). + Class-B POST-parse coercion — capability set is the arbiter: in-scope fields + coerced (scalar→array, decided_at null→omit, category→valid/default), + out-of-scope dropped; temporal_refs → schema-valid raw/resolved only. + +Harvest map: scratchpad/extract_batch_craft_harvest.md. Boundary: OSS. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Callable, Literal, TypedDict + +from synapt.extract.finalize import finalize_extraction + +# Terminal per-unit failure reasons (Q5). A Literal (not an Enum) so the spec's +# get_args(BatchFailureReason) reads the members. "merged" is reserved for a future +# batch-all path; the per-unit v1 path never emits it. +BatchFailureReason = Literal["unparseable", "schema_invalid", "dropped", "merged"] + + +class BatchInferRequest(TypedDict): + """The exact request the injected `infer` seam receives (Q-D). No unit id / + boundary tag ever appears here — boundaries stay in extract_batch bookkeeping, + out of model-visible text.""" + + prompt: str + messages: list[dict[str, str]] + capabilities: list[str] + + +# The injected inference seam (Q4): request → completion. The caller (recall) passes +# a model-backed callable; tests pass a deterministic/recorded one. Zero recall dep. +Inferer = Callable[[BatchInferRequest], str] + + +@dataclass +class BatchUnit: + """One pre-identified unit to extract (Q1). ``id`` is stable and rides into the + output as ``source_unit_id`` so merge/split/drop is detectable. ``capabilities`` + optionally overrides the per-call default for this unit.""" + + id: str + text: str + capabilities: list[str] | None = None + + +@dataclass +class BatchUnitResult: + """Per-unit outcome (Q5). ``status`` "ok" sets ``extraction``; "failed" sets + ``reason``. ``source_unit_id`` ties the slot back to its BatchUnit.""" + + source_unit_id: str + status: str # "ok" | "failed" + extraction: Any | None = None # a finalized SynaptExtraction, or None + reason: BatchFailureReason | None = None + + +async def extract_batch( + units: list[BatchUnit], + *, + infer: Inferer, + produced_by: str, + capabilities: list[str] | None = None, +) -> list[BatchUnitResult]: + """Shape + validate a batch of pre-identified units into per-unit envelopes. + + COUNT-INVARIANT: returns exactly one BatchUnitResult per input unit, in a 1:1 + slot mapping (Q5). extract_batch owns the reliability orchestration (v1 = + per-unit calls with one deterministic retry per failed unit) driven through the + injected ``infer`` seam, with zero dependency on any specific model client (Q4). + ``capabilities`` defaults to the standard profile when omitted (Q3). + + SKELETON — body is NotImplementedError; the impl lands in the follow-up PR. + """ + raise NotImplementedError( + "extract_batch skeleton conforms to the pinned contract + spec; the " + "implementation lands in the impl PR (recall#868)." + ) + + +# --- Intended internal decomposition (stubs; bodies in the impl PR) ------------ + +def _strip_output_hygiene(raw: str) -> str: + """Class-A PRE-parse (NET-NEW): strip ``` fences + ``//`` comments so grounded- + but-wrapped JSON parses. STRING-LITERAL-AWARE — a ``//`` inside a JSON string + value (e.g. ``https://…``) is preserved; only real line-comments are removed.""" + raise NotImplementedError + + +def _coerce_shape(parsed: dict, capabilities: list[str]) -> dict: + """Class-B POST-parse (harvest ``_sanitize_stage1_output`` whitelist backbone): + the capability set is the arbiter (Q2) — in-scope fields coerced (scalar→array, + ``decided_at`` null→omit, ``category``→valid/default), out-of-scope dropped; + ``temporal_refs`` coerced to schema-valid ``raw``/``resolved`` only.""" + raise NotImplementedError diff --git a/packages/python/src/synapt_extract/builder.py b/packages/python/src/synapt/extract/builder.py similarity index 99% rename from packages/python/src/synapt_extract/builder.py rename to packages/python/src/synapt/extract/builder.py index 3f9d93d..104c1f1 100644 --- a/packages/python/src/synapt_extract/builder.py +++ b/packages/python/src/synapt/extract/builder.py @@ -5,8 +5,8 @@ from dataclasses import dataclass from typing import Any -from synapt_extract.finalize import FinalizeContext, FinalizeResult, finalize_extraction -from synapt_extract.prompt import ( +from synapt.extract.finalize import FinalizeContext, FinalizeResult, finalize_extraction +from synapt.extract.prompt import ( CANONICAL_ORDER, STANDARD_EMBEDDING_INPUTS, build_extraction_prompt, diff --git a/packages/python/src/synapt_extract/extract.py b/packages/python/src/synapt/extract/extract.py similarity index 99% rename from packages/python/src/synapt_extract/extract.py rename to packages/python/src/synapt/extract/extract.py index 0966474..98c82ce 100644 --- a/packages/python/src/synapt_extract/extract.py +++ b/packages/python/src/synapt/extract/extract.py @@ -7,15 +7,15 @@ from dataclasses import dataclass, field from typing import Any, Awaitable, Callable, Literal, Protocol, TypeAlias, TypedDict -from synapt_extract.builder import DEFAULT_RESPONSE_FORMAT_NAME, ExtractionBuilder -from synapt_extract.finalize import FinalizeContext -from synapt_extract.prompt import ( +from synapt.extract.builder import DEFAULT_RESPONSE_FORMAT_NAME, ExtractionBuilder +from synapt.extract.finalize import FinalizeContext +from synapt.extract.prompt import ( STANDARD_EMBEDDING_INPUTS as STANDARD_EMBEDDING_INPUT_NAMES, capability_embedding_input, capability_embedding_preference, capability_name, ) -from synapt_extract.validate import ValidationResult +from synapt.extract.validate import ValidationResult JsonObject = dict[str, Any] diff --git a/packages/python/src/synapt_extract/finalize.py b/packages/python/src/synapt/extract/finalize.py similarity index 99% rename from packages/python/src/synapt_extract/finalize.py rename to packages/python/src/synapt/extract/finalize.py index c860869..f04d19a 100644 --- a/packages/python/src/synapt_extract/finalize.py +++ b/packages/python/src/synapt/extract/finalize.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from typing import Any -from synapt_extract.validate import ValidationResult, validate_extraction +from synapt.extract.validate import ValidationResult, validate_extraction @dataclass diff --git a/packages/python/src/synapt_extract/openai.py b/packages/python/src/synapt/extract/openai.py similarity index 98% rename from packages/python/src/synapt_extract/openai.py rename to packages/python/src/synapt/extract/openai.py index f788807..f5b5e40 100644 --- a/packages/python/src/synapt_extract/openai.py +++ b/packages/python/src/synapt/extract/openai.py @@ -10,8 +10,8 @@ from pathlib import Path from typing import Any -from synapt_extract.artifacts import create_artifact_bundle, write_artifact_bundle -from synapt_extract.extract import ( +from synapt.extract.artifacts import create_artifact_bundle, write_artifact_bundle +from synapt.extract.extract import ( EmbeddingRequest, EmbeddingResponse, ExtractResult, diff --git a/packages/python/src/synapt_extract/prompt.py b/packages/python/src/synapt/extract/prompt.py similarity index 98% rename from packages/python/src/synapt_extract/prompt.py rename to packages/python/src/synapt/extract/prompt.py index 81232a2..23b73c0 100644 --- a/packages/python/src/synapt_extract/prompt.py +++ b/packages/python/src/synapt/extract/prompt.py @@ -7,10 +7,12 @@ from pathlib import Path from typing import Any -from synapt_extract.schema import EXTRACTION_CAPABILITIES +from synapt.extract.schema import EXTRACTION_CAPABILITIES _INSTALLED_PROMPTS = Path(__file__).resolve().parent / "prompts" -_REPO_PROMPTS = Path(__file__).resolve().parents[4] / "prompts" +# parents[5] → repo root: this module sits at src/synapt/extract/prompt.py. +# (Pre-PEP420 it was one directory shallower and used parents[4].) +_REPO_PROMPTS = Path(__file__).resolve().parents[5] / "prompts" PROMPTS_DIR = _INSTALLED_PROMPTS if _INSTALLED_PROMPTS.is_dir() else _REPO_PROMPTS diff --git a/packages/python/src/synapt_extract/schema.py b/packages/python/src/synapt/extract/schema.py similarity index 100% rename from packages/python/src/synapt_extract/schema.py rename to packages/python/src/synapt/extract/schema.py diff --git a/packages/python/src/synapt_extract/schemas/action/v1.json b/packages/python/src/synapt/extract/schemas/action/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/action/v1.json rename to packages/python/src/synapt/extract/schemas/action/v1.json diff --git a/packages/python/src/synapt_extract/schemas/assertion-signals/v1.json b/packages/python/src/synapt/extract/schemas/assertion-signals/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/assertion-signals/v1.json rename to packages/python/src/synapt/extract/schemas/assertion-signals/v1.json diff --git a/packages/python/src/synapt_extract/schemas/decision/v1.json b/packages/python/src/synapt/extract/schemas/decision/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/decision/v1.json rename to packages/python/src/synapt/extract/schemas/decision/v1.json diff --git a/packages/python/src/synapt_extract/schemas/embedding/v1.json b/packages/python/src/synapt/extract/schemas/embedding/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/embedding/v1.json rename to packages/python/src/synapt/extract/schemas/embedding/v1.json diff --git a/packages/python/src/synapt_extract/schemas/entity/v1.json b/packages/python/src/synapt/extract/schemas/entity/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/entity/v1.json rename to packages/python/src/synapt/extract/schemas/entity/v1.json diff --git a/packages/python/src/synapt_extract/schemas/extract/v1.json b/packages/python/src/synapt/extract/schemas/extract/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/extract/v1.json rename to packages/python/src/synapt/extract/schemas/extract/v1.json diff --git a/packages/python/src/synapt_extract/schemas/goal/v1.json b/packages/python/src/synapt/extract/schemas/goal/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/goal/v1.json rename to packages/python/src/synapt/extract/schemas/goal/v1.json diff --git a/packages/python/src/synapt_extract/schemas/producer/v1.json b/packages/python/src/synapt/extract/schemas/producer/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/producer/v1.json rename to packages/python/src/synapt/extract/schemas/producer/v1.json diff --git a/packages/python/src/synapt_extract/schemas/question/v1.json b/packages/python/src/synapt/extract/schemas/question/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/question/v1.json rename to packages/python/src/synapt/extract/schemas/question/v1.json diff --git a/packages/python/src/synapt_extract/schemas/sentiment/v1.json b/packages/python/src/synapt/extract/schemas/sentiment/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/sentiment/v1.json rename to packages/python/src/synapt/extract/schemas/sentiment/v1.json diff --git a/packages/python/src/synapt_extract/schemas/source-metadata/v1.json b/packages/python/src/synapt/extract/schemas/source-metadata/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/source-metadata/v1.json rename to packages/python/src/synapt/extract/schemas/source-metadata/v1.json diff --git a/packages/python/src/synapt_extract/schemas/source-ref/v1.json b/packages/python/src/synapt/extract/schemas/source-ref/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/source-ref/v1.json rename to packages/python/src/synapt/extract/schemas/source-ref/v1.json diff --git a/packages/python/src/synapt_extract/schemas/temporal-ref/v1.json b/packages/python/src/synapt/extract/schemas/temporal-ref/v1.json similarity index 100% rename from packages/python/src/synapt_extract/schemas/temporal-ref/v1.json rename to packages/python/src/synapt/extract/schemas/temporal-ref/v1.json diff --git a/packages/python/src/synapt_extract/validate.py b/packages/python/src/synapt/extract/validate.py similarity index 99% rename from packages/python/src/synapt_extract/validate.py rename to packages/python/src/synapt/extract/validate.py index 18832de..c19ed53 100644 --- a/packages/python/src/synapt_extract/validate.py +++ b/packages/python/src/synapt/extract/validate.py @@ -6,7 +6,7 @@ from dataclasses import dataclass, field from typing import Any -from synapt_extract.schema import EXTRACTION_CAPABILITIES +from synapt.extract.schema import EXTRACTION_CAPABILITIES VALID_GOAL_STATUSES = frozenset(["open", "resolved", "abandoned", "in_progress"]) VALID_TEMPORAL_TYPES = frozenset(["point", "range", "duration", "unresolved"]) diff --git a/tests/python/test_conformance.py b/tests/python/test_conformance.py index be708b9..b40f03e 100644 --- a/tests/python/test_conformance.py +++ b/tests/python/test_conformance.py @@ -8,9 +8,9 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages" / "python" / "src")) -from synapt_extract.finalize import finalize_extraction, FinalizeContext -from synapt_extract.prompt import build_extraction_prompt, resolve_capabilities -from synapt_extract.validate import validate_extraction +from synapt.extract.finalize import finalize_extraction, FinalizeContext +from synapt.extract.prompt import build_extraction_prompt, resolve_capabilities +from synapt.extract.validate import validate_extraction FIXTURES_DIR = Path(__file__).resolve().parents[1] / "conformance" diff --git a/tests/python/test_extract.py b/tests/python/test_extract.py index 825201e..90fba3b 100644 --- a/tests/python/test_extract.py +++ b/tests/python/test_extract.py @@ -10,7 +10,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages" / "python" / "src")) -from synapt_extract import create_extraction_builder, extract, extract_openai +from synapt.extract import create_extraction_builder, extract, extract_openai SAMPLE_TEXT = ( diff --git a/tests/python/test_finalize.py b/tests/python/test_finalize.py index b15fe5e..248081d 100644 --- a/tests/python/test_finalize.py +++ b/tests/python/test_finalize.py @@ -9,7 +9,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages" / "python" / "src")) -from synapt_extract.finalize import finalize_extraction, FinalizeContext +from synapt.extract.finalize import finalize_extraction, FinalizeContext def _llm_output(**overrides): diff --git a/tests/python/test_prompt.py b/tests/python/test_prompt.py index 6ee6b9c..682a5c2 100644 --- a/tests/python/test_prompt.py +++ b/tests/python/test_prompt.py @@ -10,7 +10,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages" / "python" / "src")) -from synapt_extract import ( +from synapt.extract import ( ExtractionBuilder, build_finalized_extraction_schema, build_extraction_response_format, @@ -433,7 +433,7 @@ def test_full_profile_is_superset_of_standard(self): assert standard.issubset(full) def test_full_profile_includes_all_capabilities(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.schema import EXTRACTION_CAPABILITIES profiles_dir = Path(__file__).resolve().parents[2] / "prompts" / "profiles" data = json.loads((profiles_dir / "full.json").read_text()) caps = set(data["capabilities"]) @@ -443,20 +443,20 @@ def test_full_profile_includes_all_capabilities(self): class TestRegistryConsistency: def test_capability_registry_covers_schema_capabilities_in_canonical_order(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES - from synapt_extract.prompt import CAPABILITY_REGISTRY, CANONICAL_ORDER + from synapt.extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.prompt import CAPABILITY_REGISTRY, CANONICAL_ORDER assert [definition["name"] for definition in CAPABILITY_REGISTRY["capabilities"]] == CANONICAL_ORDER assert set(CANONICAL_ORDER) == EXTRACTION_CAPABILITIES def test_capability_registry_profiles_match_legacy_profile_files(self): - from synapt_extract.prompt import CAPABILITY_REGISTRY + from synapt.extract.prompt import CAPABILITY_REGISTRY profiles_dir = Path(__file__).resolve().parents[2] / "prompts" / "profiles" for profile in ("minimal", "standard", "full"): file_profile = json.loads((profiles_dir / f"{profile}.json").read_text())["capabilities"] assert CAPABILITY_REGISTRY["profiles"][profile] == file_profile def test_capability_registry_exposes_embedding_inputs(self): - from synapt_extract.prompt import STANDARD_EMBEDDING_INPUTS, capability_embedding_input + from synapt.extract.prompt import STANDARD_EMBEDDING_INPUTS, capability_embedding_input assert capability_embedding_input("entities") == "entities" assert capability_embedding_input("entity_state") == "entities" assert capability_embedding_input("structured_sentiment") == "sentiment" @@ -477,13 +477,13 @@ def test_capability_registry_exposes_embedding_inputs(self): ] def test_every_capability_has_fragment_file(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.schema import EXTRACTION_CAPABILITIES prompts_dir = Path(__file__).resolve().parents[2] / "prompts" / "v1" for cap in EXTRACTION_CAPABILITIES: assert (prompts_dir / f"{cap}.txt").exists(), f"missing fragment for {cap}" def test_every_fragment_is_valid_capability(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.schema import EXTRACTION_CAPABILITIES prompts_dir = Path(__file__).resolve().parents[2] / "prompts" / "v1" for txt_file in prompts_dir.glob("*.txt"): name = txt_file.stem @@ -492,25 +492,25 @@ def test_every_fragment_is_valid_capability(self): assert name in EXTRACTION_CAPABILITIES, f"orphan fragment: {name}" def test_canonical_order_covers_all_capabilities(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES - from synapt_extract.prompt import CANONICAL_ORDER + from synapt.extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.prompt import CANONICAL_ORDER assert set(CANONICAL_ORDER) == EXTRACTION_CAPABILITIES def test_canonical_order_has_no_duplicates(self): - from synapt_extract.prompt import CANONICAL_ORDER + from synapt.extract.prompt import CANONICAL_ORDER assert len(CANONICAL_ORDER) == len(set(CANONICAL_ORDER)) def test_capability_deps_reference_valid_capabilities(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES - from synapt_extract.prompt import CAPABILITY_DEPS + from synapt.extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.prompt import CAPABILITY_DEPS for cap, deps in CAPABILITY_DEPS.items(): assert cap in EXTRACTION_CAPABILITIES, f"dep key {cap} not a valid capability" for dep in deps: assert dep in EXTRACTION_CAPABILITIES, f"dep {dep} (from {cap}) not valid" def test_capability_rules_reference_valid_capabilities(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES - from synapt_extract.prompt import CAPABILITY_RULES + from synapt.extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.prompt import CAPABILITY_RULES for cap in CAPABILITY_RULES: assert cap in EXTRACTION_CAPABILITIES, f"rule key {cap} not a valid capability" @@ -521,7 +521,7 @@ def test_full_profile_has_no_duplicates(self): assert len(caps) == len(set(caps)), "full profile has duplicate capabilities" def test_build_prompt_succeeds_for_every_capability(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.schema import EXTRACTION_CAPABILITIES modifier_only = {"assertion_signals", "evidence_anchoring"} for cap in EXTRACTION_CAPABILITIES: caps = ["entities", cap] if cap in modifier_only else [cap] diff --git a/tests/python/test_validate.py b/tests/python/test_validate.py index 0d18aa9..3b06fe6 100644 --- a/tests/python/test_validate.py +++ b/tests/python/test_validate.py @@ -10,7 +10,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "packages" / "python" / "src")) -from synapt_extract.validate import validate_extraction +from synapt.extract.validate import validate_extraction def _minimal_extraction(**overrides): @@ -279,7 +279,7 @@ def test_unknown_capability(self): assert any("psychic_powers" in e.message for e in result.errors) def test_all_valid_capabilities(self): - from synapt_extract.schema import EXTRACTION_CAPABILITIES + from synapt.extract.schema import EXTRACTION_CAPABILITIES doc = _minimal_extraction(capabilities=sorted(EXTRACTION_CAPABILITIES)) result = validate_extraction(doc) assert result.valid