wassitai 0.2.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.
- wassitai-0.2.0/CHANGELOG.md +46 -0
- wassitai-0.2.0/LICENSE +21 -0
- wassitai-0.2.0/MANIFEST.in +9 -0
- wassitai-0.2.0/PKG-INFO +224 -0
- wassitai-0.2.0/README.md +184 -0
- wassitai-0.2.0/llm_parser/__init__.py +116 -0
- wassitai-0.2.0/pyproject.toml +86 -0
- wassitai-0.2.0/pytest.ini +47 -0
- wassitai-0.2.0/setup.cfg +4 -0
- wassitai-0.2.0/tests/README.md +354 -0
- wassitai-0.2.0/tests/__init__.py +15 -0
- wassitai-0.2.0/tests/api/__init__.py +0 -0
- wassitai-0.2.0/tests/api/conftest.py +9 -0
- wassitai-0.2.0/tests/api/test_deps.py +20 -0
- wassitai-0.2.0/tests/api/test_error_mapping.py +23 -0
- wassitai-0.2.0/tests/api/test_generate_route.py +63 -0
- wassitai-0.2.0/tests/api/test_health.py +6 -0
- wassitai-0.2.0/tests/api/test_models.py +17 -0
- wassitai-0.2.0/tests/api/test_parse_route.py +36 -0
- wassitai-0.2.0/tests/api/test_providers_route.py +5 -0
- wassitai-0.2.0/tests/api/test_spa.py +24 -0
- wassitai-0.2.0/tests/conftest.py +411 -0
- wassitai-0.2.0/tests/e2e/scenarios/test_real_world_scenarios.py +568 -0
- wassitai-0.2.0/tests/fixtures/__init__.py +6 -0
- wassitai-0.2.0/tests/fixtures/test_data.py +274 -0
- wassitai-0.2.0/tests/integration/providers/test_llm_parser_integration.py +661 -0
- wassitai-0.2.0/tests/integration/test_strict_validation_ui.py +388 -0
- wassitai-0.2.0/tests/integration/test_ui_error_handling.py +386 -0
- wassitai-0.2.0/tests/newengine/__init__.py +0 -0
- wassitai-0.2.0/tests/newengine/fakes.py +16 -0
- wassitai-0.2.0/tests/newengine/test_client.py +49 -0
- wassitai-0.2.0/tests/newengine/test_compat_shim.py +41 -0
- wassitai-0.2.0/tests/newengine/test_config.py +13 -0
- wassitai-0.2.0/tests/newengine/test_dict_adapter.py +46 -0
- wassitai-0.2.0/tests/newengine/test_errors.py +29 -0
- wassitai-0.2.0/tests/newengine/test_import_alias.py +106 -0
- wassitai-0.2.0/tests/newengine/test_json_string_adapter.py +18 -0
- wassitai-0.2.0/tests/newengine/test_native_strategy.py +18 -0
- wassitai-0.2.0/tests/newengine/test_openai_compatible.py +87 -0
- wassitai-0.2.0/tests/newengine/test_packaging.py +11 -0
- wassitai-0.2.0/tests/newengine/test_pipeline.py +28 -0
- wassitai-0.2.0/tests/newengine/test_pipeline_base.py +20 -0
- wassitai-0.2.0/tests/newengine/test_pipeline_properties.py +25 -0
- wassitai-0.2.0/tests/newengine/test_prompt_repair_strategy.py +17 -0
- wassitai-0.2.0/tests/newengine/test_provider_base.py +32 -0
- wassitai-0.2.0/tests/newengine/test_provider_registry.py +30 -0
- wassitai-0.2.0/tests/newengine/test_pydantic_adapter.py +39 -0
- wassitai-0.2.0/tests/newengine/test_render.py +31 -0
- wassitai-0.2.0/tests/newengine/test_result.py +22 -0
- wassitai-0.2.0/tests/newengine/test_review_fixes.py +109 -0
- wassitai-0.2.0/tests/newengine/test_schema_registry.py +14 -0
- wassitai-0.2.0/tests/newengine/test_smoke_public_api.py +16 -0
- wassitai-0.2.0/tests/newengine/test_stages_parsing.py +37 -0
- wassitai-0.2.0/tests/newengine/test_stages_validation.py +34 -0
- wassitai-0.2.0/tests/newengine/test_strategy_resolver.py +31 -0
- wassitai-0.2.0/tests/newengine/test_tool_calling_strategy.py +31 -0
- wassitai-0.2.0/tests/newengine/test_transport.py +44 -0
- wassitai-0.2.0/tests/unit/core/test_formatter.py +435 -0
- wassitai-0.2.0/tests/unit/core/test_json_schema_validation.py +382 -0
- wassitai-0.2.0/tests/unit/core/test_model_parser.py +467 -0
- wassitai-0.2.0/tests/unit/core/test_strict_format.py +244 -0
- wassitai-0.2.0/tests/unit/core/test_strict_validation.py +427 -0
- wassitai-0.2.0/tests/unit/llms/test_providers.py +461 -0
- wassitai-0.2.0/tests/utils/__init__.py +6 -0
- wassitai-0.2.0/tests/utils/test_helpers.py +358 -0
- wassitai-0.2.0/wassitai/__init__.py +50 -0
- wassitai-0.2.0/wassitai/_version.py +1 -0
- wassitai-0.2.0/wassitai/api/__init__.py +3 -0
- wassitai-0.2.0/wassitai/api/app.py +61 -0
- wassitai-0.2.0/wassitai/api/deps.py +49 -0
- wassitai-0.2.0/wassitai/api/errors.py +53 -0
- wassitai-0.2.0/wassitai/api/models.py +42 -0
- wassitai-0.2.0/wassitai/api/routes.py +93 -0
- wassitai-0.2.0/wassitai/api/static/app.js +405 -0
- wassitai-0.2.0/wassitai/api/static/favicon.svg +9 -0
- wassitai-0.2.0/wassitai/api/static/guide.html +357 -0
- wassitai-0.2.0/wassitai/api/static/help.js +88 -0
- wassitai-0.2.0/wassitai/api/static/index.html +379 -0
- wassitai-0.2.0/wassitai/api/static/styles.css +874 -0
- wassitai-0.2.0/wassitai/api/static/theme.js +30 -0
- wassitai-0.2.0/wassitai/api/static/vendor/alpine.min.js +5 -0
- wassitai-0.2.0/wassitai/api/static/vendor/fonts/instrument-serif-italic-400.woff2 +0 -0
- wassitai-0.2.0/wassitai/api/static/vendor/fonts/instrument-serif-normal-400.woff2 +0 -0
- wassitai-0.2.0/wassitai/api/static/vendor/fonts/inter-normal-400.woff2 +0 -0
- wassitai-0.2.0/wassitai/api/static/vendor/fonts/jetbrains-mono-normal-400.woff2 +0 -0
- wassitai-0.2.0/wassitai/api/static/vendor/fonts.css +60 -0
- wassitai-0.2.0/wassitai/client.py +151 -0
- wassitai-0.2.0/wassitai/config.py +16 -0
- wassitai-0.2.0/wassitai/core/formatter.py +76 -0
- wassitai-0.2.0/wassitai/core/formatter_utils.py +78 -0
- wassitai-0.2.0/wassitai/core/model_parser.py +139 -0
- wassitai-0.2.0/wassitai/core/model_validator.py +46 -0
- wassitai-0.2.0/wassitai/core/payload_validator.py +117 -0
- wassitai-0.2.0/wassitai/core/prompt_builder.py +95 -0
- wassitai-0.2.0/wassitai/errors.py +90 -0
- wassitai-0.2.0/wassitai/llm_parser.py +63 -0
- wassitai-0.2.0/wassitai/llms/__init__.py +22 -0
- wassitai-0.2.0/wassitai/llms/base.py +12 -0
- wassitai-0.2.0/wassitai/llms/const.py +8 -0
- wassitai-0.2.0/wassitai/llms/providers/deepseek_provider.py +21 -0
- wassitai-0.2.0/wassitai/llms/providers/gemini_provider.py +24 -0
- wassitai-0.2.0/wassitai/llms/providers/groq_provider.py +28 -0
- wassitai-0.2.0/wassitai/llms/providers/huggingface_provider.py +56 -0
- wassitai-0.2.0/wassitai/llms/providers/openai_provider.py +21 -0
- wassitai-0.2.0/wassitai/llms/providers/openrouter_provider.py +39 -0
- wassitai-0.2.0/wassitai/pipeline/__init__.py +37 -0
- wassitai-0.2.0/wassitai/pipeline/base.py +18 -0
- wassitai-0.2.0/wassitai/pipeline/context.py +25 -0
- wassitai-0.2.0/wassitai/pipeline/stages/__init__.py +0 -0
- wassitai-0.2.0/wassitai/pipeline/stages/coerce.py +11 -0
- wassitai-0.2.0/wassitai/pipeline/stages/deserialize.py +19 -0
- wassitai-0.2.0/wassitai/pipeline/stages/extract.py +33 -0
- wassitai-0.2.0/wassitai/pipeline/stages/normalize.py +12 -0
- wassitai-0.2.0/wassitai/pipeline/stages/syntax_repair.py +17 -0
- wassitai-0.2.0/wassitai/pipeline/stages/validate.py +23 -0
- wassitai-0.2.0/wassitai/providers/__init__.py +68 -0
- wassitai-0.2.0/wassitai/providers/base.py +73 -0
- wassitai-0.2.0/wassitai/providers/openai_compatible.py +107 -0
- wassitai-0.2.0/wassitai/providers/transport.py +60 -0
- wassitai-0.2.0/wassitai/render.py +28 -0
- wassitai-0.2.0/wassitai/result.py +40 -0
- wassitai-0.2.0/wassitai/schema/__init__.py +43 -0
- wassitai-0.2.0/wassitai/schema/base.py +12 -0
- wassitai-0.2.0/wassitai/schema/dict_adapter.py +80 -0
- wassitai-0.2.0/wassitai/schema/json_string_adapter.py +37 -0
- wassitai-0.2.0/wassitai/schema/pydantic_adapter.py +51 -0
- wassitai-0.2.0/wassitai/schema/spec.py +12 -0
- wassitai-0.2.0/wassitai/strategies/__init__.py +34 -0
- wassitai-0.2.0/wassitai/strategies/base.py +15 -0
- wassitai-0.2.0/wassitai/strategies/native.py +23 -0
- wassitai-0.2.0/wassitai/strategies/prompt_repair.py +26 -0
- wassitai-0.2.0/wassitai/strategies/tool_calling.py +27 -0
- wassitai-0.2.0/wassitai.egg-info/PKG-INFO +224 -0
- wassitai-0.2.0/wassitai.egg-info/SOURCES.txt +135 -0
- wassitai-0.2.0/wassitai.egg-info/dependency_links.txt +1 -0
- wassitai-0.2.0/wassitai.egg-info/requires.txt +17 -0
- wassitai-0.2.0/wassitai.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. This project adheres to
|
|
4
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
## 0.2.0
|
|
7
|
+
|
|
8
|
+
First release published to PyPI, as **`wassitai`**.
|
|
9
|
+
|
|
10
|
+
### Renamed
|
|
11
|
+
|
|
12
|
+
- **The distribution and import package are now `wassitai`.** Install with `pip install wassitai`
|
|
13
|
+
and `import wassitai`. The previous name, `llm_parser`, was never published to PyPI.
|
|
14
|
+
- `llm_parser` remains importable as a **deprecated alias** and emits a `DeprecationWarning`. It
|
|
15
|
+
forwards every attribute and submodule to `wassitai` — `from llm_parser import Client`,
|
|
16
|
+
`from llm_parser.llm_parser import LLMParser` and `llm_parser.core.formatter` all resolve to the
|
|
17
|
+
same objects as their `wassitai.*` counterparts, so pickles, `isinstance` checks and module-level
|
|
18
|
+
state are shared, not duplicated. The alias is removed in 2.0.
|
|
19
|
+
- **Breaking: the REST API moved from the top-level `api` package to `wassitai.api`.** Run it with
|
|
20
|
+
`uvicorn wassitai.api.app:app` and import from `wassitai.api.*`. Shipping a top-level `api` module
|
|
21
|
+
would have squatted a very generic import name in every environment that installed this package.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- The new engine: `Client` / `parse` / `try_parse` / `render`, `Result` with per-stage diagnostics and
|
|
26
|
+
a confidence score, and the four extension interfaces (`Provider`, `Strategy`, `SchemaAdapter`,
|
|
27
|
+
`Stage`).
|
|
28
|
+
- REST API and browser playground (`pip install "wassitai[api]"`).
|
|
29
|
+
- Packaging metadata for publication: long description, license, project URLs and classifiers, plus a
|
|
30
|
+
`LICENSE` file (MIT, as the README already declared).
|
|
31
|
+
|
|
32
|
+
### Deprecated
|
|
33
|
+
|
|
34
|
+
- `LLMParser.ask` — use `Client.generate` / `parse`. Still works, still supports Gemini and
|
|
35
|
+
HuggingFace, removed in 2.0.
|
|
36
|
+
- The `wassitai.llms.providers.*` classes — new providers are presets or `Provider` implementations
|
|
37
|
+
under `wassitai/providers/`.
|
|
38
|
+
|
|
39
|
+
### Upgrading from an `llm_parser` checkout
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip uninstall llm_parser # pip will not replace it: the distribution name changed
|
|
43
|
+
pip install wassitai
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Nothing else is required — existing `import llm_parser` code keeps working until 2.0, with a warning.
|
wassitai-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Abdelaziz Kella
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# The sdist is what distro packagers and `pip install --no-binary` build from, so
|
|
2
|
+
# it carries the things the wheel has no need for: the changelog, and the test
|
|
3
|
+
# suite that proves the tarball is what it claims to be. `packages.find` still
|
|
4
|
+
# excludes `tests*` from the *installed* set -- shipping them here does not put a
|
|
5
|
+
# top-level `tests` package into anyone's site-packages.
|
|
6
|
+
include CHANGELOG.md
|
|
7
|
+
recursive-include tests *.py
|
|
8
|
+
include tests/README.md
|
|
9
|
+
include pytest.ini
|
wassitai-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wassitai
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Universal structured-generation & LLM-output parsing framework
|
|
5
|
+
Author-email: Abdelaziz Kella <kaa.kella.abdelaziz@gmail.com>, Abdelaziz Kella <a.kella@univ-chlef.dz>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kaaaziz/wassitai
|
|
8
|
+
Project-URL: Repository, https://github.com/kaaaziz/wassitai
|
|
9
|
+
Project-URL: Issues, https://github.com/kaaaziz/wassitai/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: pydantic>=2
|
|
25
|
+
Requires-Dist: httpx>=0.24
|
|
26
|
+
Requires-Dist: json_repair>=0.25
|
|
27
|
+
Requires-Dist: xmltodict>=0.13
|
|
28
|
+
Provides-Extra: api
|
|
29
|
+
Requires-Dist: fastapi>=0.110; extra == "api"
|
|
30
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == "api"
|
|
31
|
+
Requires-Dist: python-multipart>=0.0.9; extra == "api"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
35
|
+
Requires-Dist: hypothesis>=6; extra == "dev"
|
|
36
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff<0.17,>=0.16; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# Wassitai
|
|
42
|
+
|
|
43
|
+
Ask an LLM a question, get back a **validated Python object** — not a string you have to parse.
|
|
44
|
+
|
|
45
|
+
Wassitai sends your prompt with a schema, picks the best structured-output method the provider
|
|
46
|
+
supports, then extracts, repairs, validates and coerces the reply through a deterministic pipeline.
|
|
47
|
+
If the result still doesn't fit the schema, it re-asks the model. Ships as the importable package
|
|
48
|
+
`wassitai`, with a REST API and a browser playground on top.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install wassitai # library
|
|
52
|
+
pip install "wassitai[api]" # + REST API and playground
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Code written against the pre-0.2 name still works: `llm_parser` remains importable as a deprecated
|
|
56
|
+
alias for `wassitai` until 2.0. If you have an old editable install of the `llm_parser` *distribution*,
|
|
57
|
+
run `pip uninstall llm_parser` first — pip won't replace it, and its source tree would keep shadowing
|
|
58
|
+
the alias.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- 🤖 **Any OpenAI-compatible provider** — Groq, OpenAI, DeepSeek and OpenRouter as presets;
|
|
63
|
+
Together, xAI, Ollama, vLLM or a local server with just `base_url=` and `model=`
|
|
64
|
+
- 🧱 **Five ways to write a schema** — Pydantic model, `{"field": "type"}` dict, JSON Schema, or a
|
|
65
|
+
JSON or XML schema string
|
|
66
|
+
- 🔁 **Object, JSON or XML out** — the same validated result, rendered however you need it
|
|
67
|
+
- 📊 **Lists** — ask for `list[Invoice]`, get a list back
|
|
68
|
+
- 🧪 **Repairs before it fails** — lifts JSON out of markdown fences and prose, fixes trailing
|
|
69
|
+
commas, smart quotes and Python-repr output, then re-asks the model if it still doesn't fit
|
|
70
|
+
- 🔒 **Strict or flexible validation** — exact types for production, coercion while prototyping
|
|
71
|
+
- 💡 **Diagnostics you can act on** — every stage reports what it did, a confidence score falls as
|
|
72
|
+
repairs accumulate, and errors are a typed tree with fixes in the message
|
|
73
|
+
- 🎯 **Pydantic all the way through** — your model is the schema, the validator and the return type
|
|
74
|
+
- 🌐 **A playground, not just a library** — browser console plus a beginner's guide
|
|
75
|
+
- ✅ **329 tests** — unit, integration and end-to-end, with no network calls
|
|
76
|
+
|
|
77
|
+
## Python
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from wassitai import Client
|
|
81
|
+
from pydantic import BaseModel
|
|
82
|
+
|
|
83
|
+
class Invoice(BaseModel):
|
|
84
|
+
number: str
|
|
85
|
+
total: float
|
|
86
|
+
|
|
87
|
+
client = Client.from_provider("groq", api_key="…", model="llama-3.3-70b-versatile")
|
|
88
|
+
invoice = client.generate("Extract the invoice from: …", schema=Invoice) # -> Invoice
|
|
89
|
+
|
|
90
|
+
# A list, by asking for a list schema:
|
|
91
|
+
invoices = client.generate("Extract all invoices: …", schema=list[Invoice])
|
|
92
|
+
|
|
93
|
+
# Never raises — for batch pipelines:
|
|
94
|
+
res = client.try_generate("…", schema=Invoice) # -> Result[Invoice]
|
|
95
|
+
if res.ok:
|
|
96
|
+
print(res.value, res.confidence)
|
|
97
|
+
|
|
98
|
+
# Text you already have, no provider call:
|
|
99
|
+
from wassitai import parse, render
|
|
100
|
+
invoice = parse('{"number": "A1", "total": 9.5}', Invoice)
|
|
101
|
+
text = render(invoice, "json")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A schema can be a Pydantic model, a plain `{"field": "type"}` dict, or a JSON Schema.
|
|
105
|
+
`strict=True` enforces exact types; the default coerces close-enough values (`"3"` → `3`).
|
|
106
|
+
`max_retries` caps how often the model is re-asked (default 1).
|
|
107
|
+
|
|
108
|
+
## REST API & playground
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
uvicorn wassitai.api.app:app --reload
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Playground at `http://localhost:8000/`, beginner's guide at `/static/guide.html`,
|
|
115
|
+
OpenAPI reference at `/docs`.
|
|
116
|
+
|
|
117
|
+
| Method | Path | Does |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `POST` | `/v1/generate` | prompt + schema → validated object |
|
|
120
|
+
| `POST` | `/v1/parse` | text + schema → validated object (no provider call) |
|
|
121
|
+
| `GET` | `/v1/providers` | providers and their capabilities |
|
|
122
|
+
| `GET` | `/health` | liveness |
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
curl -X POST http://localhost:8000/v1/generate \
|
|
126
|
+
-H "X-Provider-Key: $GROQ_API_KEY" \
|
|
127
|
+
-H "Content-Type: application/json" \
|
|
128
|
+
-d '{"provider":"groq","prompt":"Extract the invoice: …",
|
|
129
|
+
"schema":{"number":"str","total":"float"}}'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Provider keys travel per-request in the `X-Provider-Key` header (or come from `<PROVIDER>_API_KEY`
|
|
133
|
+
on the server) and are never stored, logged, or pre-filled into the browser. Errors map to status
|
|
134
|
+
codes: `422` validation · `401` auth · `429` rate limit · `502` provider · `504` timeout.
|
|
135
|
+
|
|
136
|
+
## How it works
|
|
137
|
+
|
|
138
|
+
`generate()` → schema adapter → strategy → provider → **pipeline** → validated object.
|
|
139
|
+
|
|
140
|
+
- **Strategy** — resolved per provider in order `native` (JSON-schema mode) → `tool_calling` →
|
|
141
|
+
`prompt_repair`, which is the universal floor and needs nothing from the provider.
|
|
142
|
+
- **Pipeline** — six ordered stages: `normalize → extract → syntax_repair → deserialize →
|
|
143
|
+
validate → coerce`. Each records a diagnostic, and the confidence score falls as repairs and
|
|
144
|
+
re-asks accumulate.
|
|
145
|
+
- **Extending** — `Provider`, `Strategy`, `SchemaAdapter` and `Stage` are the four interfaces;
|
|
146
|
+
most providers need only a preset in `wassitai/providers/__init__.py`.
|
|
147
|
+
|
|
148
|
+
Errors form one tree under `LLMParserError`: `ConfigurationError` (your bug), `ProviderError`
|
|
149
|
+
(the network/provider's), and `ParseError` (the model's).
|
|
150
|
+
|
|
151
|
+
## Providers
|
|
152
|
+
|
|
153
|
+
Built-in presets speak the OpenAI wire format: **groq**, **openai**, **deepseek**, **openrouter**.
|
|
154
|
+
Any other OpenAI-compatible endpoint (Together, xAI, Ollama, vLLM, local) works by passing
|
|
155
|
+
`base_url=` and `model=`. Native adapters for Anthropic, Gemini and Bedrock are planned behind the
|
|
156
|
+
same `Provider` interface.
|
|
157
|
+
|
|
158
|
+
## Why Wassitai
|
|
159
|
+
|
|
160
|
+
| | Wassitai | Typical structured-output library |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| **Providers** | 4 presets, plus any OpenAI-compatible endpoint via `base_url=` | An adapter to write per provider |
|
|
163
|
+
| **Output** | A validated object, or the same data as JSON or XML text | JSON only |
|
|
164
|
+
| **Validation** | Strict *and* flexible, chosen per call | One fixed mode |
|
|
165
|
+
| **On a bad answer** | Repairs it, then re-asks the model | Raises and stops |
|
|
166
|
+
| **When it fails** | Per-stage diagnostics and a confidence score | A single validation error |
|
|
167
|
+
| **Ships a UI** | Playground + beginner's guide | Library only |
|
|
168
|
+
|
|
169
|
+
### Key differentiators
|
|
170
|
+
|
|
171
|
+
1. **Dual validation modes** — coerce close-enough values while prototyping, enforce exact types in
|
|
172
|
+
production, without changing your schema.
|
|
173
|
+
2. **It repairs before it gives up** — malformed JSON is fixed, and a reply that still doesn't fit
|
|
174
|
+
the schema is sent back to the model rather than thrown at you.
|
|
175
|
+
3. **It tells the truth about what happened** — every stage records a diagnostic, and confidence
|
|
176
|
+
drops as repairs and re-asks accumulate, so a "valid" object you shouldn't trust is visible.
|
|
177
|
+
4. **Any OpenAI-compatible endpoint** — Together, xAI, Ollama, vLLM or a local server need a URL
|
|
178
|
+
and a model name, not an adapter.
|
|
179
|
+
5. **JSON *and* XML** — the same validated object renders to either.
|
|
180
|
+
6. **A playground, not just a library** — a browser console that exercises the real REST API, with
|
|
181
|
+
a guide written for people who have never called an LLM.
|
|
182
|
+
|
|
183
|
+
### Use cases
|
|
184
|
+
|
|
185
|
+
- **Development** — flexible validation for rapid prototyping
|
|
186
|
+
- **Production** — strict validation for data integrity
|
|
187
|
+
- **Research** — compare providers and strategies on the same prompt and schema
|
|
188
|
+
- **Education** — plain-language guide and honest error messages
|
|
189
|
+
- **Experimentation** — the playground, with no code to write
|
|
190
|
+
|
|
191
|
+
## From a checkout
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
pip install -e ".[api,dev]" # editable install with the API and test deps
|
|
195
|
+
pytest # 329 tests, no network calls
|
|
196
|
+
pytest -m unit # or: integration, e2e, provider
|
|
197
|
+
pytest --cov=wassitai --cov-report=html
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
CI runs the same suite on Python 3.9–3.13, plus `ruff` over the current engine, the REST API and the
|
|
201
|
+
`llm_parser` alias (the pre-0.2 `core/` and `llms/` modules are grandfathered).
|
|
202
|
+
|
|
203
|
+
## Legacy API (removed in 2.0)
|
|
204
|
+
|
|
205
|
+
`LLMParser.ask` still works and still supports Gemini and HuggingFace, but is deprecated.
|
|
206
|
+
|
|
207
|
+
| Old | New |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `LLMParser("groq", api_key=k).ask(q, schema=S, fmt="json")` | `Client.from_provider("groq", api_key=k).generate(q, schema=S)` |
|
|
210
|
+
| `...ask(q, schema=S, many=True)` | `...generate(q, schema=list[S])` |
|
|
211
|
+
| `strict_validation=True` | `generate(..., strict=True)` |
|
|
212
|
+
| need the JSON string back | `render(obj, "json")` |
|
|
213
|
+
|
|
214
|
+
The Streamlit UI at `ui/app.py` (`streamlit run ui/app.py`) targets this legacy path; the
|
|
215
|
+
playground above is the current one.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT.
|
|
220
|
+
|
|
221
|
+
## Contact
|
|
222
|
+
|
|
223
|
+
Issues and discussions on [GitHub](https://github.com/kaaaziz/wassitai) ·
|
|
224
|
+
kaa.kella.abdelaziz@gmail.com
|
wassitai-0.2.0/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Wassitai
|
|
2
|
+
|
|
3
|
+
Ask an LLM a question, get back a **validated Python object** — not a string you have to parse.
|
|
4
|
+
|
|
5
|
+
Wassitai sends your prompt with a schema, picks the best structured-output method the provider
|
|
6
|
+
supports, then extracts, repairs, validates and coerces the reply through a deterministic pipeline.
|
|
7
|
+
If the result still doesn't fit the schema, it re-asks the model. Ships as the importable package
|
|
8
|
+
`wassitai`, with a REST API and a browser playground on top.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install wassitai # library
|
|
12
|
+
pip install "wassitai[api]" # + REST API and playground
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Code written against the pre-0.2 name still works: `llm_parser` remains importable as a deprecated
|
|
16
|
+
alias for `wassitai` until 2.0. If you have an old editable install of the `llm_parser` *distribution*,
|
|
17
|
+
run `pip uninstall llm_parser` first — pip won't replace it, and its source tree would keep shadowing
|
|
18
|
+
the alias.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- 🤖 **Any OpenAI-compatible provider** — Groq, OpenAI, DeepSeek and OpenRouter as presets;
|
|
23
|
+
Together, xAI, Ollama, vLLM or a local server with just `base_url=` and `model=`
|
|
24
|
+
- 🧱 **Five ways to write a schema** — Pydantic model, `{"field": "type"}` dict, JSON Schema, or a
|
|
25
|
+
JSON or XML schema string
|
|
26
|
+
- 🔁 **Object, JSON or XML out** — the same validated result, rendered however you need it
|
|
27
|
+
- 📊 **Lists** — ask for `list[Invoice]`, get a list back
|
|
28
|
+
- 🧪 **Repairs before it fails** — lifts JSON out of markdown fences and prose, fixes trailing
|
|
29
|
+
commas, smart quotes and Python-repr output, then re-asks the model if it still doesn't fit
|
|
30
|
+
- 🔒 **Strict or flexible validation** — exact types for production, coercion while prototyping
|
|
31
|
+
- 💡 **Diagnostics you can act on** — every stage reports what it did, a confidence score falls as
|
|
32
|
+
repairs accumulate, and errors are a typed tree with fixes in the message
|
|
33
|
+
- 🎯 **Pydantic all the way through** — your model is the schema, the validator and the return type
|
|
34
|
+
- 🌐 **A playground, not just a library** — browser console plus a beginner's guide
|
|
35
|
+
- ✅ **329 tests** — unit, integration and end-to-end, with no network calls
|
|
36
|
+
|
|
37
|
+
## Python
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from wassitai import Client
|
|
41
|
+
from pydantic import BaseModel
|
|
42
|
+
|
|
43
|
+
class Invoice(BaseModel):
|
|
44
|
+
number: str
|
|
45
|
+
total: float
|
|
46
|
+
|
|
47
|
+
client = Client.from_provider("groq", api_key="…", model="llama-3.3-70b-versatile")
|
|
48
|
+
invoice = client.generate("Extract the invoice from: …", schema=Invoice) # -> Invoice
|
|
49
|
+
|
|
50
|
+
# A list, by asking for a list schema:
|
|
51
|
+
invoices = client.generate("Extract all invoices: …", schema=list[Invoice])
|
|
52
|
+
|
|
53
|
+
# Never raises — for batch pipelines:
|
|
54
|
+
res = client.try_generate("…", schema=Invoice) # -> Result[Invoice]
|
|
55
|
+
if res.ok:
|
|
56
|
+
print(res.value, res.confidence)
|
|
57
|
+
|
|
58
|
+
# Text you already have, no provider call:
|
|
59
|
+
from wassitai import parse, render
|
|
60
|
+
invoice = parse('{"number": "A1", "total": 9.5}', Invoice)
|
|
61
|
+
text = render(invoice, "json")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
A schema can be a Pydantic model, a plain `{"field": "type"}` dict, or a JSON Schema.
|
|
65
|
+
`strict=True` enforces exact types; the default coerces close-enough values (`"3"` → `3`).
|
|
66
|
+
`max_retries` caps how often the model is re-asked (default 1).
|
|
67
|
+
|
|
68
|
+
## REST API & playground
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uvicorn wassitai.api.app:app --reload
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Playground at `http://localhost:8000/`, beginner's guide at `/static/guide.html`,
|
|
75
|
+
OpenAPI reference at `/docs`.
|
|
76
|
+
|
|
77
|
+
| Method | Path | Does |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `POST` | `/v1/generate` | prompt + schema → validated object |
|
|
80
|
+
| `POST` | `/v1/parse` | text + schema → validated object (no provider call) |
|
|
81
|
+
| `GET` | `/v1/providers` | providers and their capabilities |
|
|
82
|
+
| `GET` | `/health` | liveness |
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
curl -X POST http://localhost:8000/v1/generate \
|
|
86
|
+
-H "X-Provider-Key: $GROQ_API_KEY" \
|
|
87
|
+
-H "Content-Type: application/json" \
|
|
88
|
+
-d '{"provider":"groq","prompt":"Extract the invoice: …",
|
|
89
|
+
"schema":{"number":"str","total":"float"}}'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Provider keys travel per-request in the `X-Provider-Key` header (or come from `<PROVIDER>_API_KEY`
|
|
93
|
+
on the server) and are never stored, logged, or pre-filled into the browser. Errors map to status
|
|
94
|
+
codes: `422` validation · `401` auth · `429` rate limit · `502` provider · `504` timeout.
|
|
95
|
+
|
|
96
|
+
## How it works
|
|
97
|
+
|
|
98
|
+
`generate()` → schema adapter → strategy → provider → **pipeline** → validated object.
|
|
99
|
+
|
|
100
|
+
- **Strategy** — resolved per provider in order `native` (JSON-schema mode) → `tool_calling` →
|
|
101
|
+
`prompt_repair`, which is the universal floor and needs nothing from the provider.
|
|
102
|
+
- **Pipeline** — six ordered stages: `normalize → extract → syntax_repair → deserialize →
|
|
103
|
+
validate → coerce`. Each records a diagnostic, and the confidence score falls as repairs and
|
|
104
|
+
re-asks accumulate.
|
|
105
|
+
- **Extending** — `Provider`, `Strategy`, `SchemaAdapter` and `Stage` are the four interfaces;
|
|
106
|
+
most providers need only a preset in `wassitai/providers/__init__.py`.
|
|
107
|
+
|
|
108
|
+
Errors form one tree under `LLMParserError`: `ConfigurationError` (your bug), `ProviderError`
|
|
109
|
+
(the network/provider's), and `ParseError` (the model's).
|
|
110
|
+
|
|
111
|
+
## Providers
|
|
112
|
+
|
|
113
|
+
Built-in presets speak the OpenAI wire format: **groq**, **openai**, **deepseek**, **openrouter**.
|
|
114
|
+
Any other OpenAI-compatible endpoint (Together, xAI, Ollama, vLLM, local) works by passing
|
|
115
|
+
`base_url=` and `model=`. Native adapters for Anthropic, Gemini and Bedrock are planned behind the
|
|
116
|
+
same `Provider` interface.
|
|
117
|
+
|
|
118
|
+
## Why Wassitai
|
|
119
|
+
|
|
120
|
+
| | Wassitai | Typical structured-output library |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| **Providers** | 4 presets, plus any OpenAI-compatible endpoint via `base_url=` | An adapter to write per provider |
|
|
123
|
+
| **Output** | A validated object, or the same data as JSON or XML text | JSON only |
|
|
124
|
+
| **Validation** | Strict *and* flexible, chosen per call | One fixed mode |
|
|
125
|
+
| **On a bad answer** | Repairs it, then re-asks the model | Raises and stops |
|
|
126
|
+
| **When it fails** | Per-stage diagnostics and a confidence score | A single validation error |
|
|
127
|
+
| **Ships a UI** | Playground + beginner's guide | Library only |
|
|
128
|
+
|
|
129
|
+
### Key differentiators
|
|
130
|
+
|
|
131
|
+
1. **Dual validation modes** — coerce close-enough values while prototyping, enforce exact types in
|
|
132
|
+
production, without changing your schema.
|
|
133
|
+
2. **It repairs before it gives up** — malformed JSON is fixed, and a reply that still doesn't fit
|
|
134
|
+
the schema is sent back to the model rather than thrown at you.
|
|
135
|
+
3. **It tells the truth about what happened** — every stage records a diagnostic, and confidence
|
|
136
|
+
drops as repairs and re-asks accumulate, so a "valid" object you shouldn't trust is visible.
|
|
137
|
+
4. **Any OpenAI-compatible endpoint** — Together, xAI, Ollama, vLLM or a local server need a URL
|
|
138
|
+
and a model name, not an adapter.
|
|
139
|
+
5. **JSON *and* XML** — the same validated object renders to either.
|
|
140
|
+
6. **A playground, not just a library** — a browser console that exercises the real REST API, with
|
|
141
|
+
a guide written for people who have never called an LLM.
|
|
142
|
+
|
|
143
|
+
### Use cases
|
|
144
|
+
|
|
145
|
+
- **Development** — flexible validation for rapid prototyping
|
|
146
|
+
- **Production** — strict validation for data integrity
|
|
147
|
+
- **Research** — compare providers and strategies on the same prompt and schema
|
|
148
|
+
- **Education** — plain-language guide and honest error messages
|
|
149
|
+
- **Experimentation** — the playground, with no code to write
|
|
150
|
+
|
|
151
|
+
## From a checkout
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install -e ".[api,dev]" # editable install with the API and test deps
|
|
155
|
+
pytest # 329 tests, no network calls
|
|
156
|
+
pytest -m unit # or: integration, e2e, provider
|
|
157
|
+
pytest --cov=wassitai --cov-report=html
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
CI runs the same suite on Python 3.9–3.13, plus `ruff` over the current engine, the REST API and the
|
|
161
|
+
`llm_parser` alias (the pre-0.2 `core/` and `llms/` modules are grandfathered).
|
|
162
|
+
|
|
163
|
+
## Legacy API (removed in 2.0)
|
|
164
|
+
|
|
165
|
+
`LLMParser.ask` still works and still supports Gemini and HuggingFace, but is deprecated.
|
|
166
|
+
|
|
167
|
+
| Old | New |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `LLMParser("groq", api_key=k).ask(q, schema=S, fmt="json")` | `Client.from_provider("groq", api_key=k).generate(q, schema=S)` |
|
|
170
|
+
| `...ask(q, schema=S, many=True)` | `...generate(q, schema=list[S])` |
|
|
171
|
+
| `strict_validation=True` | `generate(..., strict=True)` |
|
|
172
|
+
| need the JSON string back | `render(obj, "json")` |
|
|
173
|
+
|
|
174
|
+
The Streamlit UI at `ui/app.py` (`streamlit run ui/app.py`) targets this legacy path; the
|
|
175
|
+
playground above is the current one.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT.
|
|
180
|
+
|
|
181
|
+
## Contact
|
|
182
|
+
|
|
183
|
+
Issues and discussions on [GitHub](https://github.com/kaaaziz/wassitai) ·
|
|
184
|
+
kaa.kella.abdelaziz@gmail.com
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Deprecated import alias for :mod:`wassitai`.
|
|
2
|
+
|
|
3
|
+
The distribution was renamed ``llm_parser`` -> ``wassitai`` in 0.2. This package
|
|
4
|
+
exists only so code written against the old name keeps working: it forwards
|
|
5
|
+
every attribute *and* every submodule to :mod:`wassitai`, so both
|
|
6
|
+
|
|
7
|
+
from llm_parser import Client, parse
|
|
8
|
+
from llm_parser.llm_parser import LLMParser
|
|
9
|
+
from llm_parser.core import formatter
|
|
10
|
+
|
|
11
|
+
resolve to the very same objects the ``wassitai.*`` paths give you -- not copies.
|
|
12
|
+
It will be removed in 2.0; import from ``wassitai`` instead.
|
|
13
|
+
|
|
14
|
+
Two deliberate limits, neither of which any caller in this repo relies on:
|
|
15
|
+
``pkgutil.iter_modules(llm_parser.__path__)`` enumerates nothing (this directory
|
|
16
|
+
really is empty), and ``python -m llm_parser.<mod>`` does not work. Use the
|
|
17
|
+
``wassitai`` names for either.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import importlib
|
|
23
|
+
import importlib.abc
|
|
24
|
+
import importlib.util
|
|
25
|
+
import sys
|
|
26
|
+
import warnings
|
|
27
|
+
|
|
28
|
+
import wassitai as _wassitai
|
|
29
|
+
|
|
30
|
+
_OLD = "llm_parser"
|
|
31
|
+
_NEW = "wassitai"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _AliasLoader(importlib.abc.Loader):
|
|
35
|
+
"""Loader that hands back an already-imported ``wassitai`` submodule."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, real_name: str) -> None:
|
|
38
|
+
self._real_name = real_name
|
|
39
|
+
self._real_spec = None
|
|
40
|
+
|
|
41
|
+
def create_module(self, spec):
|
|
42
|
+
real = importlib.import_module(self._real_name)
|
|
43
|
+
self._real_spec = real.__spec__
|
|
44
|
+
return real
|
|
45
|
+
|
|
46
|
+
def exec_module(self, module) -> None:
|
|
47
|
+
# The real module was executed under its own name; re-running it would
|
|
48
|
+
# duplicate its state (registries, module-level singletons) under a
|
|
49
|
+
# second identity, which is exactly what this alias exists to prevent.
|
|
50
|
+
#
|
|
51
|
+
# Undo the one thing the import machinery did to it on the way here:
|
|
52
|
+
# `_init_module_attrs` overwrites `__spec__` unconditionally, so
|
|
53
|
+
# importing `llm_parser.core.formatter` would otherwise rename the live
|
|
54
|
+
# `wassitai.core.formatter` and blank its `submodule_search_locations` --
|
|
55
|
+
# breaking `reload()` and `importlib.resources` for callers that never
|
|
56
|
+
# touched the old name. The alias forwards one way only.
|
|
57
|
+
if self._real_spec is not None:
|
|
58
|
+
module.__spec__ = self._real_spec
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _AliasFinder(importlib.abc.MetaPathFinder):
|
|
62
|
+
"""Maps ``llm_parser.<sub>`` onto ``wassitai.<sub>``."""
|
|
63
|
+
|
|
64
|
+
# Duck-typed marker rather than isinstance: re-importing this module (tests
|
|
65
|
+
# do, to observe the warning) rebinds the class, so an isinstance check
|
|
66
|
+
# against the fresh class would miss the finder already on sys.meta_path and
|
|
67
|
+
# stack up a duplicate on every reload.
|
|
68
|
+
_wassitai_alias_finder = True
|
|
69
|
+
|
|
70
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
71
|
+
if not fullname.startswith(_OLD + "."):
|
|
72
|
+
return None
|
|
73
|
+
real_name = _NEW + fullname[len(_OLD) :]
|
|
74
|
+
try:
|
|
75
|
+
real = importlib.import_module(real_name)
|
|
76
|
+
except ModuleNotFoundError as exc:
|
|
77
|
+
if exc.name != real_name:
|
|
78
|
+
# The module exists but its *own* imports failed -- a missing
|
|
79
|
+
# optional dependency, say. Swallowing that would report the
|
|
80
|
+
# alias as absent and destroy the real cause.
|
|
81
|
+
raise
|
|
82
|
+
return None # genuinely absent: let the machinery raise as usual
|
|
83
|
+
return importlib.util.spec_from_loader(
|
|
84
|
+
fullname,
|
|
85
|
+
_AliasLoader(real_name),
|
|
86
|
+
is_package=hasattr(real, "__path__"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Ahead of the path-based finder, which would otherwise fail to find submodules
|
|
91
|
+
# that no longer live in this (now almost empty) directory.
|
|
92
|
+
if not any(getattr(f, "_wassitai_alias_finder", False) for f in sys.meta_path):
|
|
93
|
+
sys.meta_path.insert(0, _AliasFinder())
|
|
94
|
+
|
|
95
|
+
warnings.warn(
|
|
96
|
+
"The 'llm_parser' package was renamed to 'wassitai'; install it with "
|
|
97
|
+
"`pip install wassitai` and import `wassitai`. The 'llm_parser' alias will "
|
|
98
|
+
"be removed in 2.0.",
|
|
99
|
+
DeprecationWarning,
|
|
100
|
+
stacklevel=2,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
__all__ = list(_wassitai.__all__)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def __getattr__(name: str):
|
|
107
|
+
# Delegated rather than star-imported so the alias never drifts from the
|
|
108
|
+
# real package's surface as that surface grows.
|
|
109
|
+
try:
|
|
110
|
+
return getattr(_wassitai, name)
|
|
111
|
+
except AttributeError:
|
|
112
|
+
raise AttributeError(f"module {_OLD!r} has no attribute {name!r}") from None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def __dir__():
|
|
116
|
+
return sorted(set(__all__) | set(globals()))
|