contextgem 0.25.0__tar.gz → 0.26.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {contextgem-0.25.0 → contextgem-0.26.0}/.gitignore +3 -0
- contextgem-0.26.0/LICENSES.md +115 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/PKG-INFO +7 -5
- {contextgem-0.25.0 → contextgem-0.26.0}/README.md +2 -1
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/__init__.py +1 -1
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/aspects.py +22 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/documents.py +79 -2
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/llms.py +20 -20
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/serialization.py +43 -2
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/prompts/extract_aspect_items.j2 +3 -3
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/tools/schema_generator.py +83 -19
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/strings_to_types.py +19 -8
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/types.py +1 -1
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/types_to_strings.py +39 -27
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/llms.py +8 -3
- {contextgem-0.25.0 → contextgem-0.26.0}/pyproject.toml +8 -7
- {contextgem-0.25.0 → contextgem-0.26.0}/LICENSE +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/NOTICE +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/abstract.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/attrs.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/concepts.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/data_models.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/examples.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/images.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/instances.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/items.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/md_text.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/mixins.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/paras_and_sents.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/pipelines.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/base/utils.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/converters/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/converters/docx/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/converters/docx/base.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/converters/docx/namespaces.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/converters/docx/package.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/converters/docx/utils.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/data_models.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/decorators.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/exceptions.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/items.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/llm_output_structs/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/llm_output_structs/aspect_structs.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/llm_output_structs/concept_structs.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/llm_output_structs/utils.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/loggers.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/prompts/extract_concept_items.j2 +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/registry.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/suppressions.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/system/default_system_message.j2 +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/tools/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/tools/docstring_parser.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/typed_class_utils.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/types_normalization.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/user_type_hints_validation.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/validators.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/utils.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/aspects.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/concepts.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/converters/__init__.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/converters/docx.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/data_models.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/decorators.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/documents.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/examples.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/images.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/paragraphs.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/pipelines.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/sentences.py +0 -0
- {contextgem-0.25.0 → contextgem-0.26.0}/contextgem/public/utils.py +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# License Analysis Report
|
|
2
|
+
|
|
3
|
+
**Project license:** Apache-2.0
|
|
4
|
+
|
|
5
|
+
|Package|Ecosystem|Group|Source|License|Risk|Status|
|
|
6
|
+
|---------|-----------|-------|--------|---------|------|--------|
|
|
7
|
+
|[aiolimiter (1.2.1)](https://pypi.org/project/aiolimiter/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
8
|
+
|[colorlog (6.12.0)](https://pypi.org/project/colorlog/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
9
|
+
| └─ [colorama (0.4.6)](https://pypi.org/project/colorama/) (also: litellm, openai +1 more)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
10
|
+
|[docstring-parser (0.18.0)](https://pypi.org/project/docstring-parser/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
11
|
+
|[fastjsonschema (2.22.1)](https://pypi.org/project/fastjsonschema/)|python|prod|[pyproject.toml](pyproject.toml)|[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
12
|
+
|[genai-prices (0.0.72)](https://pypi.org/project/genai-prices/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
13
|
+
| └─ [annotated-types (0.8.0)](https://pypi.org/project/annotated-types/) (also: litellm, openai +1 more)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
14
|
+
| └─ [anyio (4.14.2)](https://pypi.org/project/anyio/) (also: litellm, openai +1 more)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
15
|
+
| └─ [exceptiongroup (1.3.1)](https://pypi.org/project/exceptiongroup/) (also: litellm, openai +1 more)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
16
|
+
| └─ [h11 (0.16.0)](https://pypi.org/project/h11/) (also: litellm, openai +1 more)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
17
|
+
| └─ [httpcore2 (2.9.1)](https://pypi.org/project/httpcore2/)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
18
|
+
| └─ [httpx2 (2.9.1)](https://pypi.org/project/httpx2/)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
19
|
+
| └─ [idna (3.18.0)](https://pypi.org/project/idna/) (also: litellm, openai +1 more)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
20
|
+
| └─ [pydantic-core (2.46.4)](https://pypi.org/project/pydantic-core/) (also: litellm, openai +1 more)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
21
|
+
| └─ [truststore (0.10.4)](https://pypi.org/project/truststore/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
22
|
+
| └─ [typing-inspection (0.4.2)](https://pypi.org/project/typing-inspection/) (also: litellm, openai +1 more)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
23
|
+
|[jinja2 (3.1.6)](https://pypi.org/project/jinja2/)|python|prod|[pyproject.toml](pyproject.toml)|[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
24
|
+
| └─ [markupsafe (3.0.3)](https://pypi.org/project/markupsafe/) (also: litellm)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
25
|
+
|[litellm (1.93.0)](https://pypi.org/project/litellm/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
26
|
+
| └─ [aiohappyeyeballs (2.7.1)](https://pypi.org/project/aiohappyeyeballs/)|python|prod||[PSF-2.0](https://spdx.org/licenses/PSF-2.0.html)|permissive|✓|
|
|
27
|
+
| └─ [aiohttp (3.14.3)](https://pypi.org/project/aiohttp/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) AND [MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
28
|
+
| └─ [aiosignal (1.4.0)](https://pypi.org/project/aiosignal/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
29
|
+
| └─ [async-timeout (5.0.1)](https://pypi.org/project/async-timeout/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
30
|
+
| └─ [attrs (26.1.0)](https://pypi.org/project/attrs/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
31
|
+
| └─ [certifi (2026.7.22)](https://pypi.org/project/certifi/) (also: openai, wtpsplit-lite)|python|prod||[MPL-2.0](https://spdx.org/licenses/MPL-2.0.html)|weak-copyleft|⚠|
|
|
32
|
+
| └─ [charset-normalizer (3.4.9)](https://pypi.org/project/charset-normalizer/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
33
|
+
| └─ [click (8.4.2)](https://pypi.org/project/click/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
34
|
+
| └─ [distro (1.9.0)](https://pypi.org/project/distro/) (also: openai)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
35
|
+
| └─ [fastuuid (0.14.0)](https://pypi.org/project/fastuuid/)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
36
|
+
| └─ [filelock (3.32.0)](https://pypi.org/project/filelock/) (also: wtpsplit-lite)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
37
|
+
| └─ [frozenlist (1.8.0)](https://pypi.org/project/frozenlist/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
38
|
+
| └─ [fsspec (2026.7.0)](https://pypi.org/project/fsspec/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
39
|
+
| └─ [hf-xet (1.5.2)](https://pypi.org/project/hf-xet/) (also: wtpsplit-lite)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
40
|
+
| └─ [httpcore (1.0.9)](https://pypi.org/project/httpcore/) (also: openai, wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
41
|
+
| └─ [httpx (0.28.1)](https://pypi.org/project/httpx/) (also: openai, wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
42
|
+
| └─ [huggingface-hub (1.25.1)](https://pypi.org/project/huggingface-hub/) (also: wtpsplit-lite)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
43
|
+
| └─ [importlib-metadata (8.9.0)](https://pypi.org/project/importlib-metadata/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
44
|
+
| └─ [jiter (0.16.0)](https://pypi.org/project/jiter/) (also: openai)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
45
|
+
| └─ [jsonschema (4.26.0)](https://pypi.org/project/jsonschema/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
46
|
+
| └─ [jsonschema-specifications (2025.9.1)](https://pypi.org/project/jsonschema-specifications/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
47
|
+
| └─ [multidict (6.7.1)](https://pypi.org/project/multidict/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
48
|
+
| └─ [packaging (26.2.0)](https://pypi.org/project/packaging/) (also: onnxruntime, wtpsplit-lite)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) OR [BSD-2-Clause](https://spdx.org/licenses/BSD-2-Clause.html)|permissive|✓|
|
|
49
|
+
| └─ [propcache (0.5.2)](https://pypi.org/project/propcache/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
50
|
+
| └─ [pyyaml (6.0.3)](https://pypi.org/project/pyyaml/) (also: wtpsplit-lite)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
51
|
+
| └─ [referencing (0.37.0)](https://pypi.org/project/referencing/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
52
|
+
| └─ [regex (2026.7.19)](https://pypi.org/project/regex/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) AND [CNRI-Python](https://spdx.org/licenses/CNRI-Python.html)|unknown|?|
|
|
53
|
+
| └─ [requests (2.34.2)](https://pypi.org/project/requests/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
54
|
+
| └─ [rpds-py (0.30.0)](https://pypi.org/project/rpds-py/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
55
|
+
| └─ [rpds-py (2026.6.3)](https://pypi.org/project/rpds-py/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
56
|
+
| └─ [sniffio (1.3.1)](https://pypi.org/project/sniffio/) (also: openai)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) OR [MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
57
|
+
| └─ [tiktoken (0.13.0)](https://pypi.org/project/tiktoken/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
58
|
+
| └─ [tokenizers (0.23.1)](https://pypi.org/project/tokenizers/) (also: wtpsplit-lite)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
59
|
+
| └─ [tqdm (4.70.0)](https://pypi.org/project/tqdm/) (also: openai, wtpsplit-lite)|python|prod||[MIT](https://spdx.org/licenses/MIT.html) AND [MPL-2.0](https://spdx.org/licenses/MPL-2.0.html)|weak-copyleft|⚠|
|
|
60
|
+
| └─ [urllib3 (2.7.0)](https://pypi.org/project/urllib3/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
61
|
+
| └─ [yarl (1.24.5)](https://pypi.org/project/yarl/)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
62
|
+
| └─ [zipp (4.1.0)](https://pypi.org/project/zipp/)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
63
|
+
|[lxml (6.1.1)](https://pypi.org/project/lxml/)|python|prod|[pyproject.toml](pyproject.toml)|[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
64
|
+
|[onnxruntime (1.23.2)](https://pypi.org/project/onnxruntime/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
65
|
+
| └─ [coloredlogs (15.0.1)](https://pypi.org/project/coloredlogs/) (also: wtpsplit-lite)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
66
|
+
| └─ [flatbuffers (25.12.19)](https://pypi.org/project/flatbuffers/) (also: wtpsplit-lite)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
67
|
+
| └─ [humanfriendly (10.0.0)](https://pypi.org/project/humanfriendly/) (also: wtpsplit-lite)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
68
|
+
| └─ [mpmath (1.3.0)](https://pypi.org/project/mpmath/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
69
|
+
| └─ [numpy (2.2.6)](https://pypi.org/project/numpy/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
70
|
+
| └─ [numpy (2.4.6)](https://pypi.org/project/numpy/) (also: wtpsplit-lite)|python|prod||[0BSD](https://spdx.org/licenses/0BSD.html) AND [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) AND [CC0-1.0](https://spdx.org/licenses/CC0-1.0.html) AND [MIT](https://spdx.org/licenses/MIT.html) AND [Zlib](https://spdx.org/licenses/Zlib.html)|permissive|✓|
|
|
71
|
+
| └─ [numpy (2.5.1)](https://pypi.org/project/numpy/) (also: wtpsplit-lite)|python|prod||[0BSD](https://spdx.org/licenses/0BSD.html) AND [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) AND [CC0-1.0](https://spdx.org/licenses/CC0-1.0.html) AND [MIT](https://spdx.org/licenses/MIT.html) AND [Zlib](https://spdx.org/licenses/Zlib.html)|permissive|✓|
|
|
72
|
+
| └─ [protobuf (7.35.1)](https://pypi.org/project/protobuf/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
73
|
+
| └─ [pyreadline3 (3.5.6)](https://pypi.org/project/pyreadline3/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
74
|
+
| └─ [sympy (1.14.0)](https://pypi.org/project/sympy/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
75
|
+
|[onnxruntime (1.28.0)](https://pypi.org/project/onnxruntime/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
76
|
+
| └─ [coloredlogs (15.0.1)](https://pypi.org/project/coloredlogs/) (also: wtpsplit-lite)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
77
|
+
| └─ [flatbuffers (25.12.19)](https://pypi.org/project/flatbuffers/) (also: wtpsplit-lite)|python|prod||[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
78
|
+
| └─ [humanfriendly (10.0.0)](https://pypi.org/project/humanfriendly/) (also: wtpsplit-lite)|python|prod||[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
79
|
+
| └─ [mpmath (1.3.0)](https://pypi.org/project/mpmath/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
80
|
+
| └─ [numpy (2.2.6)](https://pypi.org/project/numpy/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
81
|
+
| └─ [numpy (2.4.6)](https://pypi.org/project/numpy/) (also: wtpsplit-lite)|python|prod||[0BSD](https://spdx.org/licenses/0BSD.html) AND [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) AND [CC0-1.0](https://spdx.org/licenses/CC0-1.0.html) AND [MIT](https://spdx.org/licenses/MIT.html) AND [Zlib](https://spdx.org/licenses/Zlib.html)|permissive|✓|
|
|
82
|
+
| └─ [numpy (2.5.1)](https://pypi.org/project/numpy/) (also: wtpsplit-lite)|python|prod||[0BSD](https://spdx.org/licenses/0BSD.html) AND [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) AND [CC0-1.0](https://spdx.org/licenses/CC0-1.0.html) AND [MIT](https://spdx.org/licenses/MIT.html) AND [Zlib](https://spdx.org/licenses/Zlib.html)|permissive|✓|
|
|
83
|
+
| └─ [protobuf (7.35.1)](https://pypi.org/project/protobuf/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
84
|
+
| └─ [pyreadline3 (3.5.6)](https://pypi.org/project/pyreadline3/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
85
|
+
| └─ [sympy (1.14.0)](https://pypi.org/project/sympy/) (also: wtpsplit-lite)|python|prod||[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
86
|
+
|[openai (2.49.0)](https://pypi.org/project/openai/)|python|prod|[pyproject.toml](pyproject.toml)|[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
87
|
+
|[pillow (11.3.0)](https://pypi.org/project/pillow/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT-CMU](https://spdx.org/licenses/MIT-CMU.html)|permissive|✓|
|
|
88
|
+
|[pydantic (2.13.4)](https://pypi.org/project/pydantic/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
89
|
+
|[python-dotenv (1.2.2)](https://pypi.org/project/python-dotenv/)|python|prod|[pyproject.toml](pyproject.toml)|[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)|permissive|✓|
|
|
90
|
+
|[python-ulid (3.2.1)](https://pypi.org/project/python-ulid/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
91
|
+
|[tenacity (9.1.4)](https://pypi.org/project/tenacity/)|python|prod|[pyproject.toml](pyproject.toml)|[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)|permissive|✓|
|
|
92
|
+
|[tethered (0.5.1)](https://pypi.org/project/tethered/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
93
|
+
|[typing-extensions (4.16.0)](https://pypi.org/project/typing-extensions/)|python|prod|[pyproject.toml](pyproject.toml)|[PSF-2.0](https://spdx.org/licenses/PSF-2.0.html)|permissive|✓|
|
|
94
|
+
|[wtpsplit-lite (0.2.0)](https://pypi.org/project/wtpsplit-lite/)|python|prod|[pyproject.toml](pyproject.toml)|[MIT](https://spdx.org/licenses/MIT.html)|permissive|✓|
|
|
95
|
+
|
|
96
|
+
**Summary:** 0 violations, 2 warnings, 1 unknown, 75 ok (of which 4 reviewed)
|
|
97
|
+
|
|
98
|
+
**Completed in:** 0.66s
|
|
99
|
+
|
|
100
|
+
## Details
|
|
101
|
+
|
|
102
|
+
- ⚠ certifi uses MPL-2.0 — weak copyleft license — modifications to the licensed files must remain under the same license; linking from other files is allowed. Review whether this is acceptable for your Apache-2.0 project
|
|
103
|
+
- ? regex uses Apache-2.0 AND CNRI-Python — could not determine compatibility with Apache-2.0
|
|
104
|
+
- ⚠ tqdm uses MIT AND MPL-2.0 — weak copyleft license — modifications to the licensed files must remain under the same license; linking from other files is allowed. Review whether this is acceptable for your Apache-2.0 project
|
|
105
|
+
|
|
106
|
+
## Reviewed
|
|
107
|
+
|
|
108
|
+
|Package|Detected|Reviewed|Note|
|
|
109
|
+
|---------|----------|----------|----|
|
|
110
|
+
|[certifi (2026.7.22)](https://pypi.org/project/certifi/)|MPL-2.0|[MPL-2.0](https://spdx.org/licenses/MPL-2.0.html)|Apache-2.0 library distributed via PyPI; certifi is an unmodified upstream dependency used with dynamic linkage. MPL-2.0 is file-level weak copyleft that only triggers on modification of the MPL-licensed files — no modifications, so the obligation is dormant.|
|
|
111
|
+
|[regex (2026.7.19)](https://pypi.org/project/regex/)|Apache-2.0 AND CNRI-Python|[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) AND [CNRI-Python](https://spdx.org/licenses/CNRI-Python.html)|Flagged UNKNOWN only because licenseal cannot classify the compound expression. CNRI-Python is an OSI-approved permissive license (the historical Python license) and is compatible with Apache-2.0; regex is used unmodified with dynamic linkage. Detection-classification override.|
|
|
112
|
+
|[tqdm (4.70.0)](https://pypi.org/project/tqdm/)|MIT AND MPL-2.0|[MIT](https://spdx.org/licenses/MIT.html) AND [MPL-2.0](https://spdx.org/licenses/MPL-2.0.html)|Same rationale as certifi: unmodified upstream dependency, dynamic linkage. The MPL-2.0 portion is file-level weak copyleft and does not trigger absent modification of the MPL-licensed files.|
|
|
113
|
+
|[wtpsplit-lite (0.2.0)](https://pypi.org/project/wtpsplit-lite/)|UNKNOWN|[MIT](https://spdx.org/licenses/MIT.html)|PyPI package ships no license metadata (hence UNKNOWN); the repository LICENSE file is MIT, confirmed via GitHub's license-detection API for the superlinear-ai/wtpsplit-lite repo (spdx_id MIT). Detection override — MIT is permissive and compatible with Apache-2.0.|
|
|
114
|
+
|
|
115
|
+
_Generated by `licenseal check`. Versions shown are the resolved releases checked._
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: contextgem
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.0
|
|
4
4
|
Summary: Effortless LLM extraction from documents
|
|
5
5
|
Project-URL: Homepage, https://github.com/shcherbak-ai/contextgem
|
|
6
6
|
Project-URL: Documentation, https://github.com/shcherbak-ai/contextgem/blob/main/README.md
|
|
@@ -10,6 +10,7 @@ Author-email: shcherbak-ai <sergii@shcherbak.ai>
|
|
|
10
10
|
Maintainer-email: shcherbak-ai <sergii@shcherbak.ai>
|
|
11
11
|
License: Apache-2.0
|
|
12
12
|
License-File: LICENSE
|
|
13
|
+
License-File: LICENSES.md
|
|
13
14
|
License-File: NOTICE
|
|
14
15
|
Keywords: artificial-intelligence,aspect-extraction,automated-prompting,concept-extraction,content-extraction,context-aware,contextgem,contract-analysis,contract-automation,contract-intelligence,contract-management,contract-parsing,contract-review,data-extraction,document,document-analysis,document-extraction,document-intelligence,document-parsing,document-pipeline,document-processing,document-qa,document-understanding,entity-extraction,extraction-justifications,extraction-pipeline,fintech,generative-ai,information-extraction,insights-extraction,knowledge-extraction,large-language-models,legaltech,llm,llm-extraction,llm-framework,llm-library,llm-reasoning,low-code,machine-learning,multilingual,multimodal,neural-segmentation,nlp,no-prompt-engineering,prompt-free,question-answering,reference-mapping,semantic-analysis,structured-data,structured-data-extraction,text-analysis,text-processing,topic-extraction,unstructured-data,zero-shot
|
|
15
16
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -41,13 +42,13 @@ Requires-Dist: aiolimiter<2.0.0,>=1.2.1
|
|
|
41
42
|
Requires-Dist: colorlog<7.0.0,>=6.9.0
|
|
42
43
|
Requires-Dist: docstring-parser<1.0.0,>=0.17.0
|
|
43
44
|
Requires-Dist: fastjsonschema<3.0.0,>=2.21.2
|
|
44
|
-
Requires-Dist: genai-prices==0.0.
|
|
45
|
+
Requires-Dist: genai-prices==0.0.72
|
|
45
46
|
Requires-Dist: jinja2<4.0.0,>=3.1.5
|
|
46
|
-
Requires-Dist: litellm==1.
|
|
47
|
+
Requires-Dist: litellm==1.93.0
|
|
47
48
|
Requires-Dist: lxml<7.0.0,>=5.4.0
|
|
48
49
|
Requires-Dist: onnxruntime<1.24.0; python_version < '3.11'
|
|
49
50
|
Requires-Dist: onnxruntime>=1.24.0; python_version >= '3.14'
|
|
50
|
-
Requires-Dist: openai==2.
|
|
51
|
+
Requires-Dist: openai==2.49.0
|
|
51
52
|
Requires-Dist: pillow<12.0.0,>=11.3.0
|
|
52
53
|
Requires-Dist: pydantic<3.0.0,>=2.10.6
|
|
53
54
|
Requires-Dist: python-ulid<4.0.0,>=3.0.0
|
|
@@ -64,7 +65,7 @@ Description-Content-Type: text/markdown
|
|
|
64
65
|
| | |
|
|
65
66
|
|----------|--------|
|
|
66
67
|
| **Package** | [](https://pypi.org/project/contextgem/) [](https://pepy.tech/projects/contextgem) [](https://www.python.org/downloads/) [](https://opensource.org/licenses/Apache-2.0) |
|
|
67
|
-
| **Quality** | [](https://github.com/shcherbak-ai/contextgem/actions/workflows/ci-tests.yml) [](https://github.com/shcherbak-ai/contextgem/actions) [](https://github.com/shcherbak-ai/contextgem/actions/workflows/codeql.yml) [](https://github.com/PyCQA/bandit) [](https://www.bestpractices.dev/projects/10489) |
|
|
68
|
+
| **Quality** | [](https://github.com/shcherbak-ai/contextgem/actions/workflows/ci-tests.yml) [](https://github.com/shcherbak-ai/contextgem/actions) [](https://github.com/shcherbak-ai/contextgem/actions/workflows/codeql.yml) [](https://github.com/shcherbak-ai/contextgem/actions/workflows/licenseal.yml) [](https://github.com/PyCQA/bandit) [](https://www.bestpractices.dev/projects/10489) |
|
|
68
69
|
| **Tools** | [](https://github.com/astral-sh/uv) [](https://github.com/astral-sh/ruff) [](https://pydantic.dev) [](https://github.com/astral-sh/ty) [](https://github.com/pre-commit/pre-commit) [](https://github.com/fpgmaas/deptry) [](https://github.com/shcherbak-ai/tethered) [](https://github.com/pypa/hatch) |
|
|
69
70
|
| **Docs** | [](https://github.com/shcherbak-ai/contextgem/actions/workflows/docs.yml) [](https://shcherbak-ai.github.io/contextgem/)  [](https://deepwiki.com/shcherbak-ai/contextgem) |
|
|
70
71
|
| **Community** | [](CODE_OF_CONDUCT.md) [](https://github.com/shcherbak-ai/contextgem/issues?q=is%3Aissue+is%3Aclosed) [](https://github.com/shcherbak-ai/contextgem/commits/main) |
|
|
@@ -421,4 +422,5 @@ Built with ❤️ in Oslo, Norway.
|
|
|
421
422
|
|
|
422
423
|
| Package | Description |
|
|
423
424
|
|---------|-------------|
|
|
425
|
+
| **[licenseal](https://github.com/shcherbak-ai/licenseal)** [](https://pypi.org/project/licenseal/) [](https://github.com/shcherbak-ai/licenseal/blob/main/LICENSE) | Fast cross-ecosystem license compatibility checker for CI, audits, and enterprise adoption. Scans manifests, lockfiles, and public registry metadata without installing dependencies. |
|
|
424
426
|
| **[tethered](https://github.com/shcherbak-ai/tethered)** [](https://pypi.org/project/tethered/) [](https://github.com/shcherbak-ai/tethered/blob/main/LICENSE) | Runtime network egress control for Python. One function call blocks all unauthorized outbound connections — zero dependencies, no infrastructure changes. Ideal for supply chain defense, AI agent guardrails, and test isolation. |
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| | |
|
|
6
6
|
|----------|--------|
|
|
7
7
|
| **Package** | [](https://pypi.org/project/contextgem/) [](https://pepy.tech/projects/contextgem) [](https://www.python.org/downloads/) [](https://opensource.org/licenses/Apache-2.0) |
|
|
8
|
-
| **Quality** | [](https://github.com/shcherbak-ai/contextgem/actions/workflows/ci-tests.yml) [](https://github.com/shcherbak-ai/contextgem/actions) [](https://github.com/shcherbak-ai/contextgem/actions/workflows/codeql.yml) [](https://github.com/PyCQA/bandit) [](https://www.bestpractices.dev/projects/10489) |
|
|
8
|
+
| **Quality** | [](https://github.com/shcherbak-ai/contextgem/actions/workflows/ci-tests.yml) [](https://github.com/shcherbak-ai/contextgem/actions) [](https://github.com/shcherbak-ai/contextgem/actions/workflows/codeql.yml) [](https://github.com/shcherbak-ai/contextgem/actions/workflows/licenseal.yml) [](https://github.com/PyCQA/bandit) [](https://www.bestpractices.dev/projects/10489) |
|
|
9
9
|
| **Tools** | [](https://github.com/astral-sh/uv) [](https://github.com/astral-sh/ruff) [](https://pydantic.dev) [](https://github.com/astral-sh/ty) [](https://github.com/pre-commit/pre-commit) [](https://github.com/fpgmaas/deptry) [](https://github.com/shcherbak-ai/tethered) [](https://github.com/pypa/hatch) |
|
|
10
10
|
| **Docs** | [](https://github.com/shcherbak-ai/contextgem/actions/workflows/docs.yml) [](https://shcherbak-ai.github.io/contextgem/)  [](https://deepwiki.com/shcherbak-ai/contextgem) |
|
|
11
11
|
| **Community** | [](CODE_OF_CONDUCT.md) [](https://github.com/shcherbak-ai/contextgem/issues?q=is%3Aissue+is%3Aclosed) [](https://github.com/shcherbak-ai/contextgem/commits/main) |
|
|
@@ -362,4 +362,5 @@ Built with ❤️ in Oslo, Norway.
|
|
|
362
362
|
|
|
363
363
|
| Package | Description |
|
|
364
364
|
|---------|-------------|
|
|
365
|
+
| **[licenseal](https://github.com/shcherbak-ai/licenseal)** [](https://pypi.org/project/licenseal/) [](https://github.com/shcherbak-ai/licenseal/blob/main/LICENSE) | Fast cross-ecosystem license compatibility checker for CI, audits, and enterprise adoption. Scans manifests, lockfiles, and public registry metadata without installing dependencies. |
|
|
365
366
|
| **[tethered](https://github.com/shcherbak-ai/tethered)** [](https://pypi.org/project/tethered/) [](https://github.com/shcherbak-ai/tethered/blob/main/LICENSE) | Runtime network egress control for Python. One function call blocks all unauthorized outbound connections — zero dependencies, no infrastructure changes. Ideal for supply chain defense, AI agent guardrails, and test isolation. |
|
|
@@ -219,6 +219,28 @@ class _Aspect(
|
|
|
219
219
|
if aspect.aspects:
|
|
220
220
|
aspect._validate_and_process_sub_aspects(aspect.aspects) # ty: ignore[invalid-argument-type]
|
|
221
221
|
|
|
222
|
+
def _reset_nesting_levels(self, level: int = 0) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Recursively (re)assigns nesting levels, treating this aspect as residing
|
|
225
|
+
at ``level`` and its sub-aspects at the subsequent levels.
|
|
226
|
+
|
|
227
|
+
``_nesting_level`` is assigned relative to the parent when an aspect is
|
|
228
|
+
attached as a sub-aspect, and is never otherwise reset. When an aspect
|
|
229
|
+
that was previously used as a sub-aspect (nesting level > 0) is reused as
|
|
230
|
+
a top-level aspect (e.g. attached to a ``Document``), its stale level
|
|
231
|
+
must be normalized so that nesting-level validation and sub-aspect
|
|
232
|
+
extraction behave correctly. Top-level containers call this with
|
|
233
|
+
``level=0``.
|
|
234
|
+
|
|
235
|
+
:param level: The nesting level to assign to this aspect.
|
|
236
|
+
:type level: int
|
|
237
|
+
:return: None
|
|
238
|
+
:rtype: None
|
|
239
|
+
"""
|
|
240
|
+
self._nesting_level = level
|
|
241
|
+
for sub_aspect in self.aspects:
|
|
242
|
+
sub_aspect._reset_nesting_levels(level + 1)
|
|
243
|
+
|
|
222
244
|
@model_validator(mode="after")
|
|
223
245
|
def _validate_aspect_post(self) -> Self:
|
|
224
246
|
"""
|
|
@@ -28,7 +28,13 @@ from copy import deepcopy
|
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Annotated, Any, Literal
|
|
30
30
|
|
|
31
|
-
from pydantic import
|
|
31
|
+
from pydantic import (
|
|
32
|
+
BeforeValidator,
|
|
33
|
+
Field,
|
|
34
|
+
PrivateAttr,
|
|
35
|
+
field_validator,
|
|
36
|
+
model_validator,
|
|
37
|
+
)
|
|
32
38
|
from typing_extensions import Self
|
|
33
39
|
|
|
34
40
|
from contextgem.internal.base.aspects import _Aspect
|
|
@@ -111,6 +117,12 @@ class _Document(_AssignedInstancesProcessor, _MarkdownTextAttributesProcessor):
|
|
|
111
117
|
),
|
|
112
118
|
)
|
|
113
119
|
|
|
120
|
+
# When True, top-level aspect nesting normalization is suppressed. Set only by
|
|
121
|
+
# the extraction engine on the throwaway single-aspect documents it builds from
|
|
122
|
+
# existing (already-nested) aspects, where the added aspect must keep its
|
|
123
|
+
# nesting level so it still compares equal to the instance being processed.
|
|
124
|
+
_suppress_aspect_nesting_normalization: bool = PrivateAttr(default=False)
|
|
125
|
+
|
|
114
126
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
115
127
|
"""
|
|
116
128
|
Sets the attribute of an instance, with additional restrictions on specific attributes.
|
|
@@ -161,6 +173,34 @@ class _Document(_AssignedInstancesProcessor, _MarkdownTextAttributesProcessor):
|
|
|
161
173
|
self._set_text_from_paras()
|
|
162
174
|
self._segment_document_text()
|
|
163
175
|
|
|
176
|
+
def _normalize_top_level_aspect_nesting(self) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Normalizes the nesting levels of the document's top-level aspects to 0
|
|
179
|
+
(propagating to sub-aspects).
|
|
180
|
+
|
|
181
|
+
Aspects attached to a document are top-level. An aspect previously used
|
|
182
|
+
as a sub-aspect carries a non-zero ``_nesting_level``; left unchanged,
|
|
183
|
+
that stale level prevents assigning sub-aspects to it and breaks
|
|
184
|
+
nesting-level validation during extraction. Runs on construction and on
|
|
185
|
+
every aspect (re)assignment via the post-validation hook, so all
|
|
186
|
+
user-facing paths (constructor, ``aspects`` setter, ``add_aspects``,
|
|
187
|
+
``assign_pipeline``) are covered.
|
|
188
|
+
|
|
189
|
+
Skipped when ``_suppress_aspect_nesting_normalization`` is set, i.e. on
|
|
190
|
+
the throwaway single-aspect documents the extraction engine builds from
|
|
191
|
+
already-nested aspects — those add an existing sub-aspect and rely on it
|
|
192
|
+
comparing equal (including ``_nesting_level``) to the instance being
|
|
193
|
+
processed.
|
|
194
|
+
|
|
195
|
+
:return: None
|
|
196
|
+
:rtype: None
|
|
197
|
+
"""
|
|
198
|
+
if self._suppress_aspect_nesting_normalization:
|
|
199
|
+
return
|
|
200
|
+
for aspect in self.aspects:
|
|
201
|
+
if aspect._nesting_level != 0:
|
|
202
|
+
aspect._reset_nesting_levels(0)
|
|
203
|
+
|
|
164
204
|
def assign_pipeline(
|
|
165
205
|
self,
|
|
166
206
|
pipeline: _ExtractionPipeline | _DocumentPipeline,
|
|
@@ -479,6 +519,28 @@ class _Document(_AssignedInstancesProcessor, _MarkdownTextAttributesProcessor):
|
|
|
479
519
|
:raises ValueError: If paragraphs are not matched or ordered correctly in
|
|
480
520
|
the document text, or if `_md_text` is provided without `raw_text`.
|
|
481
521
|
"""
|
|
522
|
+
self._run_post_validation()
|
|
523
|
+
return self
|
|
524
|
+
|
|
525
|
+
def _run_post_validation(self) -> None:
|
|
526
|
+
"""
|
|
527
|
+
Performs document post-validation: normalizes top-level aspect nesting
|
|
528
|
+
levels and checks paragraph/markdown consistency.
|
|
529
|
+
|
|
530
|
+
Implemented as a plain method (separate from the ``model_validator``
|
|
531
|
+
wrapper) so it can also be invoked during deserialization, once private
|
|
532
|
+
attributes such as ``_md_text`` have been restored.
|
|
533
|
+
|
|
534
|
+
:return: None
|
|
535
|
+
:rtype: None
|
|
536
|
+
:raises ValueError: If paragraphs are not matched or ordered correctly in
|
|
537
|
+
the document text, or if `_md_text` is provided without `raw_text`.
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
# Aspects attached to a document are top-level: normalize their nesting
|
|
541
|
+
# levels (no-op when already correct, or when suppressed for the
|
|
542
|
+
# extraction engine's internal single-aspect documents).
|
|
543
|
+
self._normalize_top_level_aspect_nesting()
|
|
482
544
|
|
|
483
545
|
# Validate that _md_text cannot be populated without raw_text
|
|
484
546
|
if self._md_text and not self.raw_text:
|
|
@@ -516,4 +578,19 @@ class _Document(_AssignedInstancesProcessor, _MarkdownTextAttributesProcessor):
|
|
|
516
578
|
self.paragraphs, self._md_text, "markdown"
|
|
517
579
|
)
|
|
518
580
|
|
|
519
|
-
|
|
581
|
+
def _revalidate_after_deserialization(self) -> None:
|
|
582
|
+
"""
|
|
583
|
+
Re-runs document consistency validation after private attributes have
|
|
584
|
+
been restored during deserialization.
|
|
585
|
+
|
|
586
|
+
``from_dict`` constructs the instance from public fields (running model
|
|
587
|
+
validators) and only then restores private attributes such as
|
|
588
|
+
``_md_text``. Because the document's markdown-consistency checks are
|
|
589
|
+
gated on ``_md_text`` being populated, they are skipped at construction
|
|
590
|
+
time during deserialization; this hook re-runs them (and re-normalizes
|
|
591
|
+
aspect nesting levels) once the full state is in place.
|
|
592
|
+
|
|
593
|
+
:return: None
|
|
594
|
+
:rtype: None
|
|
595
|
+
"""
|
|
596
|
+
self._run_post_validation()
|
|
@@ -136,7 +136,7 @@ if TYPE_CHECKING:
|
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
|
|
139
|
-
litellm.suppress_debug_info = True
|
|
139
|
+
litellm.suppress_debug_info = True
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
# Local model providers supported via liteLLM
|
|
@@ -512,6 +512,11 @@ class _GenericLLMProcessor(_AbstractGenericLLMProcessor):
|
|
|
512
512
|
_Document,
|
|
513
513
|
paragraphs=aspect.reference_paragraphs,
|
|
514
514
|
)
|
|
515
|
+
# Keep the sub-aspects' nesting levels intact: the extraction engine
|
|
516
|
+
# checks "aspect is assigned to document" via instance equality (which
|
|
517
|
+
# includes `_nesting_level`), so the added aspects must stay equal to
|
|
518
|
+
# the instances being processed rather than being normalized to 0.
|
|
519
|
+
aspect_document._suppress_aspect_nesting_normalization = True
|
|
515
520
|
aspect_document.add_aspects(aspect.aspects) # ty: ignore[invalid-argument-type]
|
|
516
521
|
sub_aspect_jobs.append((aspect, aspect_document))
|
|
517
522
|
|
|
@@ -734,6 +739,11 @@ class _GenericLLMProcessor(_AbstractGenericLLMProcessor):
|
|
|
734
739
|
_Document,
|
|
735
740
|
paragraphs=sub_aspect.reference_paragraphs,
|
|
736
741
|
)
|
|
742
|
+
# Keep the sub-aspect's nesting level intact: the extraction engine
|
|
743
|
+
# checks "aspect is assigned to document" via instance equality
|
|
744
|
+
# (which includes `_nesting_level`), so the added aspect must stay
|
|
745
|
+
# equal to the instance being processed rather than normalized to 0.
|
|
746
|
+
sub_aspect_document._suppress_aspect_nesting_normalization = True
|
|
737
747
|
sub_aspect_document.add_aspects([sub_aspect])
|
|
738
748
|
sub_aspect_jobs.append((sub_aspect, sub_aspect_document))
|
|
739
749
|
|
|
@@ -3147,9 +3157,13 @@ class _DocumentLLM(_GenericLLMProcessor):
|
|
|
3147
3157
|
description="Sampling temperature [0..1]; higher values increase randomness.",
|
|
3148
3158
|
)
|
|
3149
3159
|
top_p: StrictFloat | None = Field(
|
|
3150
|
-
default=
|
|
3160
|
+
default=None,
|
|
3151
3161
|
ge=0,
|
|
3152
|
-
description=
|
|
3162
|
+
description=(
|
|
3163
|
+
"Nucleus sampling [0..1]; alternative to temperature. "
|
|
3164
|
+
"Defaults to None so only temperature is sent — some providers "
|
|
3165
|
+
"(e.g. Claude 4.x) reject requests that set both."
|
|
3166
|
+
),
|
|
3153
3167
|
)
|
|
3154
3168
|
seed: StrictInt | None = Field(
|
|
3155
3169
|
default=None,
|
|
@@ -4064,23 +4078,9 @@ class _DocumentLLM(_GenericLLMProcessor):
|
|
|
4064
4078
|
"Fallback LLM must not have the exact same config params as the main LLM."
|
|
4065
4079
|
)
|
|
4066
4080
|
|
|
4067
|
-
#
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
or self.model.startswith("openai/gpt-5")
|
|
4071
|
-
):
|
|
4072
|
-
raise ValueError(
|
|
4073
|
-
"`reasoning_effort='minimal'` is supported only for gpt-5 models."
|
|
4074
|
-
)
|
|
4075
|
-
|
|
4076
|
-
# "xhigh" reasoning effort is supported only for gpt-5.2 models
|
|
4077
|
-
if self.reasoning_effort == "xhigh" and not (
|
|
4078
|
-
self.model.startswith("azure/gpt-5.2")
|
|
4079
|
-
or self.model.startswith("openai/gpt-5.2")
|
|
4080
|
-
):
|
|
4081
|
-
raise ValueError(
|
|
4082
|
-
"`reasoning_effort='xhigh'` is supported only for gpt-5.2 models."
|
|
4083
|
-
)
|
|
4081
|
+
# Note: model-specific support for `reasoning_effort` values is not
|
|
4082
|
+
# validated here; validation is delegated to litellm / the provider API,
|
|
4083
|
+
# which raises an error at request time for unsupported combinations.
|
|
4084
4084
|
|
|
4085
4085
|
# Emit relevant warnings
|
|
4086
4086
|
|
|
@@ -92,6 +92,18 @@ KEY_RESPONSE_SUCCEEDED_PRIVATE = "_response_succeeded"
|
|
|
92
92
|
KEY_MESSAGES_PRIVATE = "_messages"
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
# Private attrs whose validation lives on a public property of the same name
|
|
96
|
+
# (without the leading underscore). During deserialization these are assigned
|
|
97
|
+
# through the public property setter so that its validation (e.g. element-type
|
|
98
|
+
# and uniqueness checks) runs, instead of writing the backing attribute directly
|
|
99
|
+
# and silently bypassing those invariants.
|
|
100
|
+
_PRIVATE_ATTR_PUBLIC_PROPERTY: dict[str, str] = {
|
|
101
|
+
KEY_EXTRACTED_ITEMS_PRIVATE: "extracted_items",
|
|
102
|
+
KEY_REFERENCE_PARAGRAPHS_PRIVATE: "reference_paragraphs",
|
|
103
|
+
KEY_REFERENCE_SENTENCES_PRIVATE: "reference_sentences",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
95
107
|
class _InstanceSerializer(BaseModel):
|
|
96
108
|
"""
|
|
97
109
|
Base class that provides reusable methods for serialization and deserialization of instances.
|
|
@@ -516,12 +528,41 @@ class _InstanceSerializer(BaseModel):
|
|
|
516
528
|
|
|
517
529
|
new_instance = cls(**constructor_kwargs)
|
|
518
530
|
|
|
519
|
-
# Set private attrs separately
|
|
531
|
+
# Set private attrs separately. Where a private attr is exposed through a
|
|
532
|
+
# public property carrying validation, assign via that property so the
|
|
533
|
+
# validation runs (instead of writing the backing attr directly and
|
|
534
|
+
# bypassing it); otherwise set the private attr directly.
|
|
520
535
|
for priv_k, priv_v in private_attrs.items():
|
|
521
|
-
|
|
536
|
+
public_name = _PRIVATE_ATTR_PUBLIC_PROPERTY.get(priv_k)
|
|
537
|
+
if public_name is not None and isinstance(
|
|
538
|
+
getattr(type(new_instance), public_name, None), property
|
|
539
|
+
):
|
|
540
|
+
setattr(new_instance, public_name, priv_v)
|
|
541
|
+
else:
|
|
542
|
+
setattr(new_instance, priv_k, priv_v)
|
|
543
|
+
|
|
544
|
+
# Re-run any validation that depends on private state restored above
|
|
545
|
+
# (and therefore could not run during construction from public fields).
|
|
546
|
+
new_instance._revalidate_after_deserialization()
|
|
522
547
|
|
|
523
548
|
return new_instance
|
|
524
549
|
|
|
550
|
+
def _revalidate_after_deserialization(self) -> None:
|
|
551
|
+
"""
|
|
552
|
+
Hook to re-run validation that depends on private attributes restored
|
|
553
|
+
after construction during :meth:`from_dict`.
|
|
554
|
+
|
|
555
|
+
Model validators run when the instance is constructed from its public
|
|
556
|
+
fields, before private attributes are restored, so any invariant gated
|
|
557
|
+
on private state is skipped at that point. Subclasses override this to
|
|
558
|
+
re-validate once the full state is in place. The base implementation is
|
|
559
|
+
a no-op.
|
|
560
|
+
|
|
561
|
+
:return: None
|
|
562
|
+
:rtype: None
|
|
563
|
+
"""
|
|
564
|
+
return None
|
|
565
|
+
|
|
525
566
|
@classmethod
|
|
526
567
|
def _convert_llm_cost_dict(cls, cost_dict: dict[str, Any]) -> _LLMCost:
|
|
527
568
|
"""
|
|
@@ -195,7 +195,7 @@ ANSWER FORMAT (MANDATORY):
|
|
|
195
195
|
[
|
|
196
196
|
{
|
|
197
197
|
"aspect_id": str,
|
|
198
|
-
"paragraphs: [
|
|
198
|
+
"paragraphs": [
|
|
199
199
|
{
|
|
200
200
|
"paragraph_id": str,
|
|
201
201
|
{% if reference_depth == "sentences" %}
|
|
@@ -220,7 +220,7 @@ ANSWER FORMAT (MANDATORY):
|
|
|
220
220
|
{
|
|
221
221
|
"aspect_id": str,
|
|
222
222
|
{% if reference_depth == "sentences" %}
|
|
223
|
-
"paragraphs: [
|
|
223
|
+
"paragraphs": [
|
|
224
224
|
{
|
|
225
225
|
"paragraph_id": str,
|
|
226
226
|
"sentences": [
|
|
@@ -233,7 +233,7 @@ ANSWER FORMAT (MANDATORY):
|
|
|
233
233
|
...
|
|
234
234
|
]
|
|
235
235
|
{% else %}
|
|
236
|
-
"paragraph_ids: [
|
|
236
|
+
"paragraph_ids": [
|
|
237
237
|
str,
|
|
238
238
|
...
|
|
239
239
|
]
|
|
@@ -77,9 +77,37 @@ _JSON_SCHEMA_TYPE_MAP: dict[type, dict[str, Any]] = {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def _json_schema_type_of_value(value: Any) -> str | None:
|
|
81
|
+
"""
|
|
82
|
+
Returns the JSON Schema ``type`` name for a concrete ``Literal`` value, or
|
|
83
|
+
``None`` if the value has no single corresponding JSON Schema scalar type.
|
|
84
|
+
|
|
85
|
+
Booleans are checked before integers because ``bool`` is a subclass of
|
|
86
|
+
``int``.
|
|
87
|
+
|
|
88
|
+
:param value: The literal value to classify.
|
|
89
|
+
:type value: Any
|
|
90
|
+
:returns: The JSON Schema type name (e.g. ``"integer"``), or ``None``.
|
|
91
|
+
:rtype: str | None
|
|
92
|
+
"""
|
|
93
|
+
if isinstance(value, bool):
|
|
94
|
+
return "boolean"
|
|
95
|
+
if isinstance(value, int):
|
|
96
|
+
return "integer"
|
|
97
|
+
if isinstance(value, float):
|
|
98
|
+
return "number"
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
return "string"
|
|
101
|
+
if value is None:
|
|
102
|
+
return "null"
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
80
106
|
def _python_type_to_json_schema(
|
|
81
107
|
type_hint: Any,
|
|
82
108
|
description: str | None = None,
|
|
109
|
+
*,
|
|
110
|
+
_seen: frozenset[int] = frozenset(),
|
|
83
111
|
) -> dict[str, Any]:
|
|
84
112
|
"""
|
|
85
113
|
Convert a Python type hint to a JSON Schema object.
|
|
@@ -98,6 +126,11 @@ def _python_type_to_json_schema(
|
|
|
98
126
|
:type type_hint: Any
|
|
99
127
|
:param description: Optional description to include in the schema.
|
|
100
128
|
:type description: str | None
|
|
129
|
+
:param _seen: Identities (``id()``) of class/TypedDict types currently being
|
|
130
|
+
expanded on the recursion stack. Used to break cycles in self- or
|
|
131
|
+
mutually-referential types; on revisit a generic ``{"type": "object"}``
|
|
132
|
+
is emitted instead of recursing without bound. Internal use only.
|
|
133
|
+
:type _seen: frozenset[int]
|
|
101
134
|
:returns: JSON Schema object.
|
|
102
135
|
:rtype: dict[str, Any]
|
|
103
136
|
:raises TypeError: If the type hint is not supported.
|
|
@@ -126,7 +159,7 @@ def _python_type_to_json_schema(
|
|
|
126
159
|
item_type = args[0] if args else Any
|
|
127
160
|
schema = {
|
|
128
161
|
"type": "array",
|
|
129
|
-
"items": _python_type_to_json_schema(item_type),
|
|
162
|
+
"items": _python_type_to_json_schema(item_type, _seen=_seen),
|
|
130
163
|
}
|
|
131
164
|
if description:
|
|
132
165
|
schema["description"] = description
|
|
@@ -140,12 +173,14 @@ def _python_type_to_json_schema(
|
|
|
140
173
|
if len(args) == 2 and args[1] is ...:
|
|
141
174
|
schema = {
|
|
142
175
|
"type": "array",
|
|
143
|
-
"items": _python_type_to_json_schema(args[0]),
|
|
176
|
+
"items": _python_type_to_json_schema(args[0], _seen=_seen),
|
|
144
177
|
}
|
|
145
178
|
else:
|
|
146
179
|
schema = {
|
|
147
180
|
"type": "array",
|
|
148
|
-
"prefixItems": [
|
|
181
|
+
"prefixItems": [
|
|
182
|
+
_python_type_to_json_schema(a, _seen=_seen) for a in args
|
|
183
|
+
],
|
|
149
184
|
"minItems": len(args),
|
|
150
185
|
"maxItems": len(args),
|
|
151
186
|
}
|
|
@@ -160,7 +195,7 @@ def _python_type_to_json_schema(
|
|
|
160
195
|
item_type = args[0] if args else Any
|
|
161
196
|
schema = {
|
|
162
197
|
"type": "array",
|
|
163
|
-
"items": _python_type_to_json_schema(item_type),
|
|
198
|
+
"items": _python_type_to_json_schema(item_type, _seen=_seen),
|
|
164
199
|
"uniqueItems": True,
|
|
165
200
|
}
|
|
166
201
|
if description:
|
|
@@ -173,7 +208,9 @@ def _python_type_to_json_schema(
|
|
|
173
208
|
value_type = args[1]
|
|
174
209
|
schema = {
|
|
175
210
|
"type": "object",
|
|
176
|
-
"additionalProperties": _python_type_to_json_schema(
|
|
211
|
+
"additionalProperties": _python_type_to_json_schema(
|
|
212
|
+
value_type, _seen=_seen
|
|
213
|
+
),
|
|
177
214
|
}
|
|
178
215
|
else:
|
|
179
216
|
schema = {"type": "object"}
|
|
@@ -193,10 +230,12 @@ def _python_type_to_json_schema(
|
|
|
193
230
|
if len(non_none_types) == 1 and has_none:
|
|
194
231
|
# This is Optional[X] - return schema for X
|
|
195
232
|
# The "required" handling happens at the parameter level
|
|
196
|
-
return _python_type_to_json_schema(
|
|
233
|
+
return _python_type_to_json_schema(
|
|
234
|
+
non_none_types[0], description, _seen=_seen
|
|
235
|
+
)
|
|
197
236
|
|
|
198
237
|
# Multiple types: use anyOf
|
|
199
|
-
schema = {"anyOf": [_python_type_to_json_schema(a) for a in args]}
|
|
238
|
+
schema = {"anyOf": [_python_type_to_json_schema(a, _seen=_seen) for a in args]}
|
|
200
239
|
if description:
|
|
201
240
|
schema["description"] = description
|
|
202
241
|
return schema
|
|
@@ -207,14 +246,13 @@ def _python_type_to_json_schema(
|
|
|
207
246
|
if not literal_values:
|
|
208
247
|
raise TypeError("Literal type must have at least one value")
|
|
209
248
|
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
schema = {"type": "string", "enum": literal_values}
|
|
249
|
+
# Only assign a JSON Schema "type" when every literal value maps to the
|
|
250
|
+
# same scalar type; a mixed-type Literal (e.g. Literal[1, "a"]) would
|
|
251
|
+
# otherwise produce an invalid/mislabeled schema (a string under
|
|
252
|
+
# "type": "integer"), so emit a bare enum in that case.
|
|
253
|
+
value_types = {_json_schema_type_of_value(v) for v in literal_values}
|
|
254
|
+
if len(value_types) == 1 and None not in value_types:
|
|
255
|
+
schema = {"type": value_types.pop(), "enum": literal_values}
|
|
218
256
|
else:
|
|
219
257
|
schema = {"enum": literal_values}
|
|
220
258
|
|
|
@@ -224,15 +262,33 @@ def _python_type_to_json_schema(
|
|
|
224
262
|
|
|
225
263
|
# Handle TypedDict
|
|
226
264
|
if is_typeddict(type_hint):
|
|
265
|
+
# Break cycles in self-/mutually-referential TypedDicts
|
|
266
|
+
if id(type_hint) in _seen:
|
|
267
|
+
schema = {"type": "object"}
|
|
268
|
+
if description:
|
|
269
|
+
schema["description"] = description
|
|
270
|
+
return schema
|
|
227
271
|
# type_hint is guaranteed to be a TypedDict type here
|
|
228
|
-
return _typeddict_to_json_schema(
|
|
272
|
+
return _typeddict_to_json_schema(
|
|
273
|
+
type_hint, # type: ignore[arg-type]
|
|
274
|
+
description,
|
|
275
|
+
_seen | {id(type_hint)},
|
|
276
|
+
)
|
|
229
277
|
|
|
230
278
|
# Handle plain classes that might be dict-like or have annotations
|
|
231
279
|
if isinstance(type_hint, type):
|
|
232
280
|
# If it's a basic class without special handling, treat as object
|
|
233
281
|
if hasattr(type_hint, "__annotations__") and type_hint.__annotations__:
|
|
282
|
+
# Break cycles in self-/mutually-referential classes
|
|
283
|
+
if id(type_hint) in _seen:
|
|
284
|
+
schema = {"type": "object"}
|
|
285
|
+
if description:
|
|
286
|
+
schema["description"] = description
|
|
287
|
+
return schema
|
|
234
288
|
# Class with annotations - try to convert
|
|
235
|
-
return _class_to_json_schema(
|
|
289
|
+
return _class_to_json_schema(
|
|
290
|
+
type_hint, description, _seen | {id(type_hint)}
|
|
291
|
+
)
|
|
236
292
|
|
|
237
293
|
# Fallback: treat as generic object
|
|
238
294
|
schema = {"type": "object"}
|
|
@@ -251,6 +307,7 @@ def _python_type_to_json_schema(
|
|
|
251
307
|
def _typeddict_to_json_schema(
|
|
252
308
|
td_type: type,
|
|
253
309
|
description: str | None = None,
|
|
310
|
+
_seen: frozenset[int] = frozenset(),
|
|
254
311
|
) -> dict[str, Any]:
|
|
255
312
|
"""
|
|
256
313
|
Convert a TypedDict to a JSON Schema object.
|
|
@@ -259,6 +316,9 @@ def _typeddict_to_json_schema(
|
|
|
259
316
|
:type td_type: type
|
|
260
317
|
:param description: Optional description to include in the schema.
|
|
261
318
|
:type description: str | None
|
|
319
|
+
:param _seen: Identities of class/TypedDict types currently being expanded,
|
|
320
|
+
used for cycle detection. Internal use only.
|
|
321
|
+
:type _seen: frozenset[int]
|
|
262
322
|
:returns: JSON Schema object.
|
|
263
323
|
:rtype: dict[str, Any]
|
|
264
324
|
"""
|
|
@@ -269,7 +329,7 @@ def _typeddict_to_json_schema(
|
|
|
269
329
|
|
|
270
330
|
properties: dict[str, Any] = {}
|
|
271
331
|
for name, hint in hints.items():
|
|
272
|
-
properties[name] = _python_type_to_json_schema(hint)
|
|
332
|
+
properties[name] = _python_type_to_json_schema(hint, _seen=_seen)
|
|
273
333
|
|
|
274
334
|
# Get required keys (TypedDict has __required_keys__ and __optional_keys__)
|
|
275
335
|
required_keys = getattr(td_type, "__required_keys__", frozenset())
|
|
@@ -296,6 +356,7 @@ def _typeddict_to_json_schema(
|
|
|
296
356
|
def _class_to_json_schema(
|
|
297
357
|
cls_type: type,
|
|
298
358
|
description: str | None = None,
|
|
359
|
+
_seen: frozenset[int] = frozenset(),
|
|
299
360
|
) -> dict[str, Any]:
|
|
300
361
|
"""
|
|
301
362
|
Convert a class with annotations to a JSON Schema object.
|
|
@@ -304,6 +365,9 @@ def _class_to_json_schema(
|
|
|
304
365
|
:type cls_type: type
|
|
305
366
|
:param description: Optional description to include in the schema.
|
|
306
367
|
:type description: str | None
|
|
368
|
+
:param _seen: Identities of class/TypedDict types currently being expanded,
|
|
369
|
+
used for cycle detection. Internal use only.
|
|
370
|
+
:type _seen: frozenset[int]
|
|
307
371
|
:returns: JSON Schema object.
|
|
308
372
|
:rtype: dict[str, Any]
|
|
309
373
|
"""
|
|
@@ -314,7 +378,7 @@ def _class_to_json_schema(
|
|
|
314
378
|
|
|
315
379
|
properties: dict[str, Any] = {}
|
|
316
380
|
for name, hint in hints.items():
|
|
317
|
-
properties[name] = _python_type_to_json_schema(hint)
|
|
381
|
+
properties[name] = _python_type_to_json_schema(hint, _seen=_seen)
|
|
318
382
|
|
|
319
383
|
schema: dict[str, Any] = {
|
|
320
384
|
"type": "object",
|
|
@@ -214,23 +214,34 @@ def _parse_type_hint(s: str, i: int = 0) -> tuple[Any, int]:
|
|
|
214
214
|
|
|
215
215
|
# Parse the literal value (string or other)
|
|
216
216
|
if i < len(s) and s[i] == '"':
|
|
217
|
-
# Parse string literal
|
|
218
|
-
|
|
217
|
+
# Parse a double-quoted string literal, honoring backslash escapes.
|
|
218
|
+
# A backslash escapes the next character (so `\\` -> `\` and
|
|
219
|
+
# `\"` -> `"`); only an unescaped `"` terminates the literal. This
|
|
220
|
+
# is the inverse of `_serialize_literal_value`, which escapes `\`
|
|
221
|
+
# and `"` when emitting a string literal value.
|
|
219
222
|
start = i
|
|
220
|
-
#
|
|
223
|
+
i += 1 # skip opening quote
|
|
224
|
+
chars: list[str] = []
|
|
225
|
+
terminated = False
|
|
221
226
|
while i < len(s):
|
|
222
|
-
|
|
227
|
+
ch = s[i]
|
|
228
|
+
if ch == "\\" and i + 1 < len(s):
|
|
229
|
+
# Consume the backslash and take the next character literally
|
|
230
|
+
chars.append(s[i + 1])
|
|
231
|
+
i += 2
|
|
232
|
+
continue
|
|
233
|
+
if ch == '"':
|
|
234
|
+
terminated = True
|
|
223
235
|
break
|
|
236
|
+
chars.append(ch)
|
|
224
237
|
i += 1
|
|
225
238
|
|
|
226
|
-
if
|
|
239
|
+
if not terminated:
|
|
227
240
|
raise ValueError(
|
|
228
241
|
f"Unterminated string literal in literal type at position {start} in {s!r}"
|
|
229
242
|
)
|
|
230
243
|
|
|
231
|
-
|
|
232
|
-
string_value = s[start:i].replace('\\"', '"')
|
|
233
|
-
values.append(string_value)
|
|
244
|
+
values.append("".join(chars))
|
|
234
245
|
i += 1 # skip closing quote
|
|
235
246
|
|
|
236
247
|
else:
|
|
@@ -91,7 +91,7 @@ DefaultDecimalField = Field(
|
|
|
91
91
|
default_factory=lambda: Decimal("0.00000"), ge=Decimal("0.00000")
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
ReasoningEffort = Literal["minimal", "low", "medium", "high", "xhigh"]
|
|
94
|
+
ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"]
|
|
95
95
|
|
|
96
96
|
TextMode = Literal["raw", "markdown"]
|
|
97
97
|
|
|
@@ -49,6 +49,42 @@ PRIMITIVE_TYPES_STRING_MAP = {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def _serialize_literal_value(value: Any) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Serializes a single ``Literal`` value to its canonical string token.
|
|
55
|
+
|
|
56
|
+
The output is the inverse of the literal-value parsing performed during
|
|
57
|
+
deserialization (``strings_to_types._parse_type_hint``), so that
|
|
58
|
+
``Literal`` round-trips losslessly:
|
|
59
|
+
|
|
60
|
+
- ``str`` -> a double-quoted string with ``\\`` and ``"`` escaped
|
|
61
|
+
- ``True`` / ``False`` -> ``"true"`` / ``"false"`` (lowercase JSON tokens)
|
|
62
|
+
- ``None`` -> ``"null"``
|
|
63
|
+
- ``int`` / ``float`` -> their plain string representation
|
|
64
|
+
|
|
65
|
+
Booleans are checked before the ``str`` branch is reached and before any
|
|
66
|
+
numeric handling, since ``bool`` is a subclass of ``int``.
|
|
67
|
+
|
|
68
|
+
:param value: The literal value to serialize.
|
|
69
|
+
:type value: Any
|
|
70
|
+
:return: The canonical string token for the literal value.
|
|
71
|
+
:rtype: str
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(value, str):
|
|
74
|
+
# Escape backslashes first, then double quotes, and wrap in double quotes.
|
|
75
|
+
# Order matters: escaping quotes first would double-escape the backslashes
|
|
76
|
+
# introduced by the quote escaping.
|
|
77
|
+
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
|
78
|
+
return f'"{escaped}"'
|
|
79
|
+
if value is True:
|
|
80
|
+
return "true"
|
|
81
|
+
if value is False:
|
|
82
|
+
return "false"
|
|
83
|
+
if value is None:
|
|
84
|
+
return "null"
|
|
85
|
+
return str(value)
|
|
86
|
+
|
|
87
|
+
|
|
52
88
|
def _raise_json_serializable_type_error(
|
|
53
89
|
invalid_type: Any,
|
|
54
90
|
field_name: str | None = None,
|
|
@@ -243,14 +279,7 @@ def _format_type(typ: Any, indent_level: int = 0) -> str:
|
|
|
243
279
|
if getattr(arg, "__origin__", None) is Literal:
|
|
244
280
|
# Extract individual literal values
|
|
245
281
|
for literal_arg in get_args(arg):
|
|
246
|
-
|
|
247
|
-
flattened_values.append(
|
|
248
|
-
'"' + literal_arg.replace('"', '\\"') + '"'
|
|
249
|
-
)
|
|
250
|
-
elif literal_arg is None:
|
|
251
|
-
flattened_values.append("null")
|
|
252
|
-
else:
|
|
253
|
-
flattened_values.append(str(literal_arg))
|
|
282
|
+
flattened_values.append(_serialize_literal_value(literal_arg))
|
|
254
283
|
elif arg is type(None):
|
|
255
284
|
flattened_values.append("null")
|
|
256
285
|
else:
|
|
@@ -268,17 +297,7 @@ def _format_type(typ: Any, indent_level: int = 0) -> str:
|
|
|
268
297
|
# Handle Literal types
|
|
269
298
|
if getattr(typ, "__origin__", None) is Literal:
|
|
270
299
|
# Create a list of serialized literal values
|
|
271
|
-
literal_values = []
|
|
272
|
-
for arg in get_args(typ):
|
|
273
|
-
if isinstance(arg, str):
|
|
274
|
-
# Escape quotes in strings and wrap in quotes
|
|
275
|
-
literal_values.append('"' + arg.replace('"', '\\"') + '"')
|
|
276
|
-
elif arg is None:
|
|
277
|
-
# Handle None consistently with standalone None type
|
|
278
|
-
literal_values.append("null")
|
|
279
|
-
else:
|
|
280
|
-
# For non-string literals, just convert to string
|
|
281
|
-
literal_values.append(str(arg))
|
|
300
|
+
literal_values = [_serialize_literal_value(arg) for arg in get_args(typ)]
|
|
282
301
|
|
|
283
302
|
# Join all literal values with "or" instead of commas
|
|
284
303
|
values_str = " or ".join(literal_values)
|
|
@@ -468,14 +487,7 @@ def _serialize_type_hint(tp: Any) -> str:
|
|
|
468
487
|
# Handle Literal types
|
|
469
488
|
elif origin is Literal:
|
|
470
489
|
# Create a list of serialized literal values
|
|
471
|
-
literal_values = []
|
|
472
|
-
for arg in args:
|
|
473
|
-
if isinstance(arg, str):
|
|
474
|
-
# Escape quotes in strings and wrap in quotes
|
|
475
|
-
literal_values.append('"' + arg.replace('"', '\\"') + '"')
|
|
476
|
-
else:
|
|
477
|
-
# For non-string literals, just convert to string
|
|
478
|
-
literal_values.append(str(arg))
|
|
490
|
+
literal_values = [_serialize_literal_value(arg) for arg in args]
|
|
479
491
|
|
|
480
492
|
# Join all literal values with commas
|
|
481
493
|
values_str = ", ".join(literal_values)
|
|
@@ -103,12 +103,17 @@ class DocumentLLM(_DocumentLLM):
|
|
|
103
103
|
(CoT-capable) models. Defaults to 16000.
|
|
104
104
|
:vartype max_completion_tokens: int
|
|
105
105
|
:ivar reasoning_effort: The effort level for the LLM to reason about the input. Can be set to
|
|
106
|
-
``"minimal"
|
|
107
|
-
|
|
106
|
+
``"none"``, ``"minimal"``, ``"low"``, ``"medium"``, ``"high"``, ``"xhigh"``, or
|
|
107
|
+
``"max"``. Support for specific values varies by model and provider; unsupported
|
|
108
|
+
combinations are rejected by litellm or the provider API at request time. Note that
|
|
109
|
+
``"none"`` explicitly disables reasoning on models that support this value, which
|
|
110
|
+
differs from the default ``None`` (parameter not sent, provider default applies).
|
|
111
|
+
Relevant for reasoning (CoT-capable) models. Defaults to None.
|
|
108
112
|
:vartype reasoning_effort: ReasoningEffort | None
|
|
109
113
|
:ivar top_p: Nucleus sampling value (0.0 to 1.0) controlling output focus/randomness.
|
|
110
114
|
Lower values make output more deterministic, higher values produce more diverse outputs.
|
|
111
|
-
Defaults to
|
|
115
|
+
Defaults to ``None`` (only ``temperature`` is sent). Some providers (e.g. Claude 4.x)
|
|
116
|
+
reject requests that set both ``temperature`` and ``top_p``.
|
|
112
117
|
:vartype top_p: float | None
|
|
113
118
|
:ivar num_retries_failed_request: Number of retries when LLM request fails. Defaults to 3.
|
|
114
119
|
:vartype num_retries_failed_request: int
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "contextgem"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.26.0"
|
|
4
4
|
description = "Effortless LLM extraction from documents"
|
|
5
5
|
authors = [{ name = "shcherbak-ai", email = "sergii@shcherbak.ai" }]
|
|
6
6
|
requires-python = ">=3.10,<3.15"
|
|
@@ -96,11 +96,11 @@ dependencies = [
|
|
|
96
96
|
"colorlog (>=6.9.0,<7.0.0)",
|
|
97
97
|
"docstring-parser (>=0.17.0,<1.0.0)",
|
|
98
98
|
"fastjsonschema (>=2.21.2,<3.0.0)",
|
|
99
|
-
"genai-prices==0.0.
|
|
99
|
+
"genai-prices==0.0.72", # pin version due to frequent API changes
|
|
100
100
|
"jinja2 (>=3.1.5,<4.0.0)",
|
|
101
|
-
"litellm==1.
|
|
101
|
+
"litellm==1.93.0", # pin version due to occasional instability between patches and minor versions
|
|
102
102
|
"lxml (>=5.4.0,<7.0.0)",
|
|
103
|
-
"openai==2.
|
|
103
|
+
"openai==2.49.0", # pinned version for stable compatibility with litellm (after issue with 1.100.0)
|
|
104
104
|
"pillow (>=11.3.0,<12.0.0)",
|
|
105
105
|
"pydantic (>=2.10.6,<3.0.0)",
|
|
106
106
|
"python-ulid (>=3.0.0,<4.0.0)",
|
|
@@ -132,12 +132,12 @@ dev = [
|
|
|
132
132
|
"nest-asyncio (>=1.6.0,<2.0.0)",
|
|
133
133
|
"pre-commit (>=4.1.0,<5.0.0)",
|
|
134
134
|
"pympler (>=1.1.0,<2.0.0)",
|
|
135
|
-
"ty (
|
|
135
|
+
"ty (==0.0.35)", # pinned to the last fully-clean ty release; newer ones surface issues here; unpin when issues are fixed
|
|
136
136
|
"pytest (>=8.3.4,<9.0.0)",
|
|
137
137
|
"pytest-cov (>=6.0.0,<7.0.0)",
|
|
138
138
|
"pytest-recording (>=0.13.4,<1.0.0)",
|
|
139
139
|
"python-dotenv (>=1.0.1,<2.0.0)",
|
|
140
|
-
"ruff (>=0.12.3,<
|
|
140
|
+
"ruff (>=0.12.3,<0.16.0)", # ruff 0.16 expands the default lint rule set, flagging many new issues; capped until a dedicated lint migration
|
|
141
141
|
"sphinx (>=7.0.0,<8.0.0)",
|
|
142
142
|
"sphinx-autodoc-typehints<3.0.0",
|
|
143
143
|
"sphinx-autobuild (>=2024.10.3,<2027.0.0)",
|
|
@@ -148,6 +148,7 @@ dev = [
|
|
|
148
148
|
"sphinxext-opengraph (>=0.9.1,<1.0.0)",
|
|
149
149
|
"types-lxml (>=2025.3.30,<2027.0.0)",
|
|
150
150
|
"twine>=6.2.0",
|
|
151
|
+
"licenseal>=0.1.1",
|
|
151
152
|
]
|
|
152
153
|
|
|
153
154
|
[build-system]
|
|
@@ -211,7 +212,7 @@ log_cli_level = "WARNING"
|
|
|
211
212
|
|
|
212
213
|
[tool.commitizen]
|
|
213
214
|
name = "cz_conventional_commits"
|
|
214
|
-
version = "0.
|
|
215
|
+
version = "0.26.0"
|
|
215
216
|
version_files = [
|
|
216
217
|
"contextgem/__init__.py:__version__",
|
|
217
218
|
"pyproject.toml:version",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/llm_output_structs/aspect_structs.py
RENAMED
|
File without changes
|
{contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/llm_output_structs/concept_structs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/prompts/extract_concept_items.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/system/default_system_message.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contextgem-0.25.0 → contextgem-0.26.0}/contextgem/internal/typings/user_type_hints_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|