llm-ports 0.12.1.dev14__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.
- llm_ports-0.12.1.dev14/.gitattributes +1 -0
- llm_ports-0.12.1.dev14/.github/workflows/ci.yml +152 -0
- llm_ports-0.12.1.dev14/.github/workflows/release.yml +110 -0
- llm_ports-0.12.1.dev14/.github/zizmor.yml +9 -0
- llm_ports-0.12.1.dev14/.gitignore +9 -0
- llm_ports-0.12.1.dev14/.pre-commit-config.yaml +13 -0
- llm_ports-0.12.1.dev14/CHANGELOG.md +237 -0
- llm_ports-0.12.1.dev14/CONTRIBUTING.md +52 -0
- llm_ports-0.12.1.dev14/LICENSE +21 -0
- llm_ports-0.12.1.dev14/PKG-INFO +170 -0
- llm_ports-0.12.1.dev14/README.md +126 -0
- llm_ports-0.12.1.dev14/ci/jscpd-baseline.json +4 -0
- llm_ports-0.12.1.dev14/docs/REDESIGN.md +337 -0
- llm_ports-0.12.1.dev14/docs/adr/0001-named-outcomes-never-raise.md +71 -0
- llm_ports-0.12.1.dev14/docs/adr/0002-grounded-prose-by-construction.md +59 -0
- llm_ports-0.12.1.dev14/docs/adr/0003-the-number-law-compares-by-value.md +61 -0
- llm_ports-0.12.1.dev14/docs/adr/0004-digit-free-context.md +49 -0
- llm_ports-0.12.1.dev14/docs/adr/0005-character-whitelist-grammar.md +47 -0
- llm_ports-0.12.1.dev14/docs/adr/0006-data-rights-in-prov-o-terms.md +48 -0
- llm_ports-0.12.1.dev14/docs/adr/0007-one-observation-lifecycle.md +52 -0
- llm_ports-0.12.1.dev14/docs/adr/0008-prompt-packs-named-by-content-sha.md +53 -0
- llm_ports-0.12.1.dev14/docs/adr/README.md +20 -0
- llm_ports-0.12.1.dev14/llm_ports/__init__.py +88 -0
- llm_ports-0.12.1.dev14/llm_ports/call.py +301 -0
- llm_ports-0.12.1.dev14/llm_ports/call_graders.py +297 -0
- llm_ports-0.12.1.dev14/llm_ports/chunking.py +94 -0
- llm_ports-0.12.1.dev14/llm_ports/claim_refute.py +200 -0
- llm_ports-0.12.1.dev14/llm_ports/content.py +252 -0
- llm_ports-0.12.1.dev14/llm_ports/context.py +77 -0
- llm_ports-0.12.1.dev14/llm_ports/failure.py +90 -0
- llm_ports-0.12.1.dev14/llm_ports/grounder_http.py +98 -0
- llm_ports-0.12.1.dev14/llm_ports/grounding.py +94 -0
- llm_ports-0.12.1.dev14/llm_ports/index_order.py +74 -0
- llm_ports-0.12.1.dev14/llm_ports/json_schema.py +99 -0
- llm_ports-0.12.1.dev14/llm_ports/judge_sampling.py +231 -0
- llm_ports-0.12.1.dev14/llm_ports/langfuse_prompt_source.py +172 -0
- llm_ports-0.12.1.dev14/llm_ports/langfuse_tracer.py +223 -0
- llm_ports-0.12.1.dev14/llm_ports/lettucedetect_grounder.py +100 -0
- llm_ports-0.12.1.dev14/llm_ports/manifest.py +115 -0
- llm_ports-0.12.1.dev14/llm_ports/numbers.py +180 -0
- llm_ports-0.12.1.dev14/llm_ports/observation.py +198 -0
- llm_ports-0.12.1.dev14/llm_ports/otel_gen_ai.py +181 -0
- llm_ports-0.12.1.dev14/llm_ports/packs.py +218 -0
- llm_ports-0.12.1.dev14/llm_ports/ports.py +671 -0
- llm_ports-0.12.1.dev14/llm_ports/py.typed +0 -0
- llm_ports-0.12.1.dev14/llm_ports/pydantic_ai_embedder.py +209 -0
- llm_ports-0.12.1.dev14/llm_ports/pydantic_ai_generator.py +383 -0
- llm_ports-0.12.1.dev14/llm_ports/pydantic_ai_replay.py +121 -0
- llm_ports-0.12.1.dev14/llm_ports/pydantic_evals_judge.py +205 -0
- llm_ports-0.12.1.dev14/llm_ports/reference.py +165 -0
- llm_ports-0.12.1.dev14/llm_ports/reranker_http.py +139 -0
- llm_ports-0.12.1.dev14/llm_ports/retrieval_metrics.py +66 -0
- llm_ports-0.12.1.dev14/llm_ports/roles.py +179 -0
- llm_ports-0.12.1.dev14/llm_ports/slots.py +168 -0
- llm_ports-0.12.1.dev14/llm_ports/sql_guard.py +214 -0
- llm_ports-0.12.1.dev14/llm_ports/testing.py +316 -0
- llm_ports-0.12.1.dev14/llm_ports/testing_langfuse.py +167 -0
- llm_ports-0.12.1.dev14/pyproject.toml +111 -0
- llm_ports-0.12.1.dev14/tests/__init__.py +0 -0
- llm_ports-0.12.1.dev14/tests/conftest.py +15 -0
- llm_ports-0.12.1.dev14/tests/golden/rubric_v0_11.json +18 -0
- llm_ports-0.12.1.dev14/tests/llm_tiers.toml +24 -0
- llm_ports-0.12.1.dev14/tests/packs/demo.multi/pack.yaml +6 -0
- llm_ports-0.12.1.dev14/tests/packs/demo.multi/prompt.j2 +1 -0
- llm_ports-0.12.1.dev14/tests/packs/demo.multi/second.j2 +1 -0
- llm_ports-0.12.1.dev14/tests/packs/demo.one/pack.yaml +5 -0
- llm_ports-0.12.1.dev14/tests/packs/demo.one/prompt.j2 +2 -0
- llm_ports-0.12.1.dev14/tests/packs/demo.refute/pack.yaml +5 -0
- llm_ports-0.12.1.dev14/tests/packs/demo.refute/prompt.j2 +5 -0
- llm_ports-0.12.1.dev14/tests/test_call.py +274 -0
- llm_ports-0.12.1.dev14/tests/test_call_graders.py +162 -0
- llm_ports-0.12.1.dev14/tests/test_chunking.py +116 -0
- llm_ports-0.12.1.dev14/tests/test_claim_refute.py +97 -0
- llm_ports-0.12.1.dev14/tests/test_content.py +94 -0
- llm_ports-0.12.1.dev14/tests/test_failure.py +53 -0
- llm_ports-0.12.1.dev14/tests/test_grounder_http.py +86 -0
- llm_ports-0.12.1.dev14/tests/test_grounding.py +54 -0
- llm_ports-0.12.1.dev14/tests/test_json_schema.py +102 -0
- llm_ports-0.12.1.dev14/tests/test_judge_sampling.py +152 -0
- llm_ports-0.12.1.dev14/tests/test_langfuse_prompt_source.py +218 -0
- llm_ports-0.12.1.dev14/tests/test_langfuse_tracer.py +197 -0
- llm_ports-0.12.1.dev14/tests/test_lettucedetect_grounder.py +96 -0
- llm_ports-0.12.1.dev14/tests/test_numbers.py +53 -0
- llm_ports-0.12.1.dev14/tests/test_observation.py +113 -0
- llm_ports-0.12.1.dev14/tests/test_otel_gen_ai.py +155 -0
- llm_ports-0.12.1.dev14/tests/test_packs.py +117 -0
- llm_ports-0.12.1.dev14/tests/test_ports_contract.py +44 -0
- llm_ports-0.12.1.dev14/tests/test_pydantic_ai_embedder.py +179 -0
- llm_ports-0.12.1.dev14/tests/test_pydantic_ai_generator.py +243 -0
- llm_ports-0.12.1.dev14/tests/test_pydantic_ai_replay.py +78 -0
- llm_ports-0.12.1.dev14/tests/test_pydantic_evals_judge.py +204 -0
- llm_ports-0.12.1.dev14/tests/test_reference.py +160 -0
- llm_ports-0.12.1.dev14/tests/test_reranker_http.py +149 -0
- llm_ports-0.12.1.dev14/tests/test_retrieval_metrics.py +57 -0
- llm_ports-0.12.1.dev14/tests/test_roles.py +81 -0
- llm_ports-0.12.1.dev14/tests/test_rubric_golden.py +51 -0
- llm_ports-0.12.1.dev14/tests/test_slots.py +89 -0
- llm_ports-0.12.1.dev14/tests/test_sql_guard.py +138 -0
- llm_ports-0.12.1.dev14/uv.lock +2777 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# llm-ports' own gate. Every job starts from `permissions: {}` and grants only what it needs.
|
|
2
|
+
#
|
|
3
|
+
# lint ruff check + ruff format --check, pre-commit (all files), actionlint + zizmor over
|
|
4
|
+
# the workflows
|
|
5
|
+
# pytest the locked suite on 3.12 / 3.13 / 3.14 / 3.15 (ubuntu) and 3.13 (windows), with a
|
|
6
|
+
# coverage floor (pyproject `[tool.coverage.report] fail_under`, raised only); one leg
|
|
7
|
+
# uploads to Codecov over OIDC.
|
|
8
|
+
# test-lowest every direct dependency at its declared lower bound (`--resolution lowest-direct`)
|
|
9
|
+
# build sdist + wheel, `twine check --strict`
|
|
10
|
+
# test the one required check: green only when every job above is
|
|
11
|
+
# gitleaks / dup-code ci-kit's reusable workflows, called the way every consumer calls them
|
|
12
|
+
#
|
|
13
|
+
# release.yml calls this whole workflow (`workflow_call`) before it builds anything to publish.
|
|
14
|
+
name: ci
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
push:
|
|
18
|
+
branches: [main]
|
|
19
|
+
pull_request:
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
workflow_call:
|
|
22
|
+
|
|
23
|
+
permissions: {}
|
|
24
|
+
|
|
25
|
+
concurrency:
|
|
26
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
27
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
lint:
|
|
31
|
+
name: lint
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
timeout-minutes: 10
|
|
34
|
+
permissions:
|
|
35
|
+
contents: read
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
38
|
+
with:
|
|
39
|
+
persist-credentials: false
|
|
40
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
41
|
+
- run: uv sync --frozen --group lint
|
|
42
|
+
- name: ruff check
|
|
43
|
+
run: uv run --no-sync ruff check --output-format=github .
|
|
44
|
+
- name: ruff format
|
|
45
|
+
run: uv run --no-sync ruff format --check .
|
|
46
|
+
- name: pre-commit
|
|
47
|
+
run: uv run --no-sync pre-commit run --all-files --show-diff-on-failure
|
|
48
|
+
- name: actionlint
|
|
49
|
+
run: uv run --no-sync actionlint
|
|
50
|
+
- name: zizmor
|
|
51
|
+
env:
|
|
52
|
+
GH_TOKEN: ${{ github.token }} # online audits (impostor commits, known-vulnerable actions)
|
|
53
|
+
run: uv run --no-sync zizmor --format=github .
|
|
54
|
+
|
|
55
|
+
pytest:
|
|
56
|
+
name: pytest (${{ matrix.os }}, ${{ matrix.python }})
|
|
57
|
+
runs-on: ${{ matrix.os }}
|
|
58
|
+
timeout-minutes: 15
|
|
59
|
+
permissions:
|
|
60
|
+
contents: read
|
|
61
|
+
id-token: write # Codecov OIDC upload, no stored token
|
|
62
|
+
strategy:
|
|
63
|
+
fail-fast: false
|
|
64
|
+
matrix:
|
|
65
|
+
os: [ubuntu-latest]
|
|
66
|
+
python: ["3.12", "3.13", "3.14", "3.15"]
|
|
67
|
+
include:
|
|
68
|
+
- { os: windows-latest, python: "3.13" }
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
71
|
+
with:
|
|
72
|
+
persist-credentials: false
|
|
73
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
74
|
+
with:
|
|
75
|
+
python-version: ${{ matrix.python }}
|
|
76
|
+
- run: uv sync --frozen
|
|
77
|
+
- name: pytest
|
|
78
|
+
run: uv run --no-sync pytest -q --cov --cov-report=term --cov-report=xml
|
|
79
|
+
- name: codecov
|
|
80
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
|
|
81
|
+
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
|
82
|
+
with:
|
|
83
|
+
use_oidc: true
|
|
84
|
+
files: coverage.xml
|
|
85
|
+
fail_ci_if_error: true
|
|
86
|
+
|
|
87
|
+
test-lowest:
|
|
88
|
+
name: test-lowest (lowest-direct, 3.12)
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
timeout-minutes: 15
|
|
91
|
+
permissions:
|
|
92
|
+
contents: read
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
95
|
+
with:
|
|
96
|
+
persist-credentials: false
|
|
97
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
98
|
+
with:
|
|
99
|
+
python-version: "3.12"
|
|
100
|
+
# Every extra CI can install (lettucedetect pulls torch; its tests run over a stub detector).
|
|
101
|
+
- run: uv venv
|
|
102
|
+
- run: uv pip install --resolution lowest-direct -e ".[evals,langfuse,otel,sql]" --group dev
|
|
103
|
+
- run: uv pip list
|
|
104
|
+
- name: pytest
|
|
105
|
+
run: .venv/bin/python -m pytest -q
|
|
106
|
+
|
|
107
|
+
build:
|
|
108
|
+
name: build
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
timeout-minutes: 10
|
|
111
|
+
permissions:
|
|
112
|
+
contents: read
|
|
113
|
+
steps:
|
|
114
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
115
|
+
with:
|
|
116
|
+
persist-credentials: false
|
|
117
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
118
|
+
- run: uv build
|
|
119
|
+
- name: twine check
|
|
120
|
+
run: uv run --frozen --only-group lint twine check --strict dist/*
|
|
121
|
+
|
|
122
|
+
# The required status check is named `test`; it aggregates every job above so a new leg is
|
|
123
|
+
# covered without editing branch protection.
|
|
124
|
+
test:
|
|
125
|
+
name: test
|
|
126
|
+
if: always()
|
|
127
|
+
needs: [lint, pytest, test-lowest, build]
|
|
128
|
+
runs-on: ubuntu-latest
|
|
129
|
+
timeout-minutes: 5
|
|
130
|
+
permissions: {}
|
|
131
|
+
steps:
|
|
132
|
+
- uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0
|
|
133
|
+
with:
|
|
134
|
+
jobs: ${{ toJSON(needs) }}
|
|
135
|
+
|
|
136
|
+
gitleaks:
|
|
137
|
+
name: gitleaks (secret scan)
|
|
138
|
+
permissions:
|
|
139
|
+
contents: read
|
|
140
|
+
pull-requests: read
|
|
141
|
+
uses: mjdevaccount/ci-kit/.github/workflows/gitleaks.yml@v1
|
|
142
|
+
with:
|
|
143
|
+
mode: history
|
|
144
|
+
|
|
145
|
+
# The same code written twice fails the PR: ci-kit's `ci-dup-code` (jscpd behind a shrink-only
|
|
146
|
+
# fingerprint baseline, ci/jscpd-baseline.json). A NEW clone >= 70 tokens / 7 lines between two
|
|
147
|
+
# tracked files fails; a removed one may re-freeze smaller (`--write-baseline` refuses to grow).
|
|
148
|
+
# Reports as "dup-code / lint".
|
|
149
|
+
dup-code:
|
|
150
|
+
permissions:
|
|
151
|
+
contents: read
|
|
152
|
+
uses: mjdevaccount/ci-kit/.github/workflows/dup-code.yml@v1
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Publishing. Two triggers, one pipeline, one index:
|
|
2
|
+
#
|
|
3
|
+
# push of a `v*` tag ci (the whole gate, reused) -> build X.Y.Z -> PyPI
|
|
4
|
+
# -> GitHub release with dist/* attached
|
|
5
|
+
# workflow_dispatch ci -> build a PEP 440 dev release -> PyPI
|
|
6
|
+
#
|
|
7
|
+
# The dispatch build is `X.Y.Z.devN` (N = this workflow's run number): pyproject's version when it is
|
|
8
|
+
# already a dev version, else the next patch (0.12.0 -> 0.12.1.devN). Installers skip pre-releases
|
|
9
|
+
# unless asked (`pip install --pre`), so a dev upload is the end-to-end dry run of a real release.
|
|
10
|
+
#
|
|
11
|
+
# Uploads use PyPI trusted publishing: GitHub's OIDC token is exchanged for a short-lived upload
|
|
12
|
+
# token, so no secret is stored. PyPI's publisher names this repo, this file and the `pypi`
|
|
13
|
+
# environment; renaming any of the three breaks publishing.
|
|
14
|
+
#
|
|
15
|
+
# The GitHub release does NOT depend on the PyPI upload: consumers pin the wheel URL
|
|
16
|
+
# llm-ports @ https://github.com/mjdevaccount/llm-ports/releases/download/vX.Y.Z/llm_ports-X.Y.Z-py3-none-any.whl
|
|
17
|
+
# and that asset must exist for every tag whatever the index did. A tag that does not match
|
|
18
|
+
# pyproject's version publishes nothing.
|
|
19
|
+
name: release
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
tags: ["v*"]
|
|
24
|
+
workflow_dispatch:
|
|
25
|
+
|
|
26
|
+
permissions: {}
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
ci:
|
|
30
|
+
name: ci
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
id-token: write # ci.yml's Codecov upload
|
|
34
|
+
pull-requests: read # ci-kit gitleaks
|
|
35
|
+
# A job-level reusable workflow resolves from this commit, never the runner's filesystem; the
|
|
36
|
+
# `$/` self-repository form is not yet parsed by actionlint 1.7.12.
|
|
37
|
+
uses: ./.github/workflows/ci.yml # zizmor: ignore[self-repository]
|
|
38
|
+
|
|
39
|
+
build:
|
|
40
|
+
name: build
|
|
41
|
+
needs: ci
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
timeout-minutes: 10
|
|
44
|
+
permissions:
|
|
45
|
+
contents: read
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
48
|
+
with:
|
|
49
|
+
persist-credentials: false
|
|
50
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
51
|
+
with:
|
|
52
|
+
enable-cache: false # a release builds from a clean resolve, never a shared cache
|
|
53
|
+
- name: tag matches pyproject version
|
|
54
|
+
if: github.event_name == 'push'
|
|
55
|
+
run: |
|
|
56
|
+
v=$(uv version --short)
|
|
57
|
+
[ "v$v" = "$GITHUB_REF_NAME" ] || { echo "::error::tag $GITHUB_REF_NAME != pyproject v$v"; exit 1; }
|
|
58
|
+
- name: dev version
|
|
59
|
+
if: github.event_name == 'workflow_dispatch'
|
|
60
|
+
run: |
|
|
61
|
+
case "$(uv version --short)" in
|
|
62
|
+
*.dev*) uv version --frozen --bump "dev=$GITHUB_RUN_NUMBER" ;;
|
|
63
|
+
*) uv version --frozen --bump patch --bump "dev=$GITHUB_RUN_NUMBER" ;;
|
|
64
|
+
esac
|
|
65
|
+
- run: uv build --no-sources
|
|
66
|
+
- name: twine check
|
|
67
|
+
run: uv run --frozen --only-group lint twine check --strict dist/*
|
|
68
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
69
|
+
with:
|
|
70
|
+
name: dist
|
|
71
|
+
path: dist/
|
|
72
|
+
if-no-files-found: error
|
|
73
|
+
|
|
74
|
+
pypi:
|
|
75
|
+
name: publish (PyPI)
|
|
76
|
+
needs: build
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
timeout-minutes: 10
|
|
79
|
+
environment:
|
|
80
|
+
name: pypi
|
|
81
|
+
url: https://pypi.org/project/llm-ports/
|
|
82
|
+
permissions:
|
|
83
|
+
id-token: write # trusted publishing; the action also uploads PEP 740 attestations
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
86
|
+
with:
|
|
87
|
+
name: dist
|
|
88
|
+
path: dist/
|
|
89
|
+
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
90
|
+
|
|
91
|
+
github-release:
|
|
92
|
+
name: GitHub release
|
|
93
|
+
if: github.event_name == 'push'
|
|
94
|
+
needs: build # never on `pypi`: the wheel URL consumers pin must exist whatever the index did
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
timeout-minutes: 10
|
|
97
|
+
permissions:
|
|
98
|
+
contents: write # create the release and upload its assets
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
101
|
+
with:
|
|
102
|
+
name: dist
|
|
103
|
+
path: dist/
|
|
104
|
+
- name: create release
|
|
105
|
+
env:
|
|
106
|
+
GH_TOKEN: ${{ github.token }}
|
|
107
|
+
run: |
|
|
108
|
+
# no --clobber fallback: a published wheel's bytes are what consumers locked a hash of
|
|
109
|
+
gh release create "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --verify-tag \
|
|
110
|
+
--title "$GITHUB_REF_NAME" --generate-notes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# zizmor (run in ci.yml's lint job). Third-party actions are pinned by commit SHA; ci-kit's
|
|
2
|
+
# reusable workflows are this owner's own and ship fixes by moving their `v1` tag, so a ref pin is
|
|
3
|
+
# the contract there.
|
|
4
|
+
rules:
|
|
5
|
+
unpinned-uses:
|
|
6
|
+
config:
|
|
7
|
+
policies:
|
|
8
|
+
"mjdevaccount/*": ref-pin
|
|
9
|
+
"*": hash-pin
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Run in CI (ci.yml, lint job) and locally after `uv run pre-commit install`.
|
|
2
|
+
repos:
|
|
3
|
+
- repo: local
|
|
4
|
+
hooks:
|
|
5
|
+
# Docstrings state the contract; deployment and incident detail lives in docs/adr and
|
|
6
|
+
# CHANGELOG.md (CONTRIBUTING.md). The lookahead admits one string: `GenRequest.label`'s
|
|
7
|
+
# default value is public behaviour until 1.0 renames it.
|
|
8
|
+
- id: no-deployment-vocabulary
|
|
9
|
+
name: no deployment vocabulary in the package
|
|
10
|
+
language: pygrep
|
|
11
|
+
entry: 'EDGE|edge-host|\bbox[_-](?!chat")|Tradier|trade[-_.]data|narrator|\b\d+B\b|20\d\d-\d\d-\d\d'
|
|
12
|
+
files: ^llm_ports/
|
|
13
|
+
types: [python]
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). Why a design is the way it is lives in
|
|
6
|
+
[`docs/adr/`](docs/adr/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Releases publish to PyPI through trusted publishing (no stored token), with PEP 740
|
|
13
|
+
attestations; a tag still attaches the wheel and sdist to a GitHub release, independently of the
|
|
14
|
+
upload. A manual run of `release.yml` uploads a `.devN` pre-release as the dry run.
|
|
15
|
+
- Package metadata: project URLs, classifiers and keywords.
|
|
16
|
+
- CI: `actionlint` over the workflows; `ci.yml` is callable (`workflow_call`), and a release runs
|
|
17
|
+
all of it before building.
|
|
18
|
+
|
|
19
|
+
## [0.12.0] - 2026-11-03
|
|
20
|
+
|
|
21
|
+
No behaviour change and no public name change; the release carries documentation, tooling and
|
|
22
|
+
dependency floors.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `numbers.law_text(mode, *, web)`: the number law's one English statement, beside the check it
|
|
27
|
+
states (`numbers.loose`). `judge_sampling.rubric` composes it; its output is byte-identical to
|
|
28
|
+
0.11.0 for every grounding mode and web flag, pinned by `tests/test_rubric_golden.py`.
|
|
29
|
+
- Architecture decision records `docs/adr/0001`–`0008` (MADR), holding the rationale and the
|
|
30
|
+
measured evidence that used to live in docstrings.
|
|
31
|
+
- `CONTRIBUTING.md`: the docstring rules, where history goes, how to release.
|
|
32
|
+
- A `pre-commit` hook that rejects deployment vocabulary in `llm_ports/`, run in CI.
|
|
33
|
+
- CI: pytest on Python 3.12, 3.13, 3.14 and 3.15 (Ubuntu) and 3.13 (Windows); the suite at every
|
|
34
|
+
dependency's declared lower bound (`--resolution lowest-direct`); a branch-coverage floor with
|
|
35
|
+
Codecov upload over OIDC; `ruff format --check`; `twine check --strict` on the build; `zizmor`
|
|
36
|
+
over the workflows; every workflow defaults to `permissions: {}`.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Every docstring states the contract in Google style, with invariants in an `Invariants:`
|
|
41
|
+
section; ruff's `D` rules (Google convention) enforce the layout.
|
|
42
|
+
- The tree is formatted with `ruff format`.
|
|
43
|
+
- Dependency floors match what is tested: `pydantic>=2.12` (already required by
|
|
44
|
+
`pydantic-ai-slim>=2.57`) and, for the `sql` extra, `sqlglot>=30.22`. Older sqlglot drops
|
|
45
|
+
`LIMIT ALL` at parse time, so `sql_guard.guard` capped the query instead of refusing it.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- `content.print_gate` and `DataClass.allows` document the `ValueError` they raise for an audience
|
|
50
|
+
outside `AUDIENCES`; the old text said `print_gate` never raised.
|
|
51
|
+
|
|
52
|
+
## [0.11.0] - 2026-11-03
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- `ports.Reranker`, the tenth port, and `Reranked` (scores in input order, order-only; never
|
|
57
|
+
raises for a model, wire or gate failure). Also exported from the package root.
|
|
58
|
+
- `reranker_http`: `HttpReranker` over the Cohere/Jina-shaped `/rerank` wire, `payload`,
|
|
59
|
+
`scores_from`. Every document is scored exactly once (indices exactly `0..n-1`) and every score
|
|
60
|
+
is finite, or the result is `invalid`.
|
|
61
|
+
- `observation`: `observe(tracer, ...)` opens one generation around a block and ends it once
|
|
62
|
+
(`ERROR` and `latency_ms` on an exception); `PortObservation` (was `call._PortObservation`) with
|
|
63
|
+
`usage_counts()` and `fail(exc, metadata)`; `opt_int`. A call that reported neither usage count
|
|
64
|
+
stamps `tokens_unmeasured: true`. `call.tracer_trace` is built on `observe`.
|
|
65
|
+
- `langfuse_tracer` (extra `langfuse`): `LangfuseTracer(client, ..., on_event=)`,
|
|
66
|
+
`LangfuseGeneration`, `usage_details`, `trace_id`.
|
|
67
|
+
- `langfuse_prompt_source`: `LangfusePromptSource(client, seed_for, ..., on_event=)`; the repo
|
|
68
|
+
leads and the label mirrors. Duck-typed client, no SDK import.
|
|
69
|
+
- `otel_gen_ai` (extra `otel`): `gen_ai_span`, `record_gen_ai` (back-dated), `GenAiRecorder`;
|
|
70
|
+
attribute names from `opentelemetry-semantic-conventions`, emitting `gen_ai.provider.name`
|
|
71
|
+
beside the deprecated `gen_ai.system`.
|
|
72
|
+
- `sql_guard` (extra `sql`): `guard(sql, policy)`, `SqlGuardPolicy` (with `dialect`, default
|
|
73
|
+
postgres), `SqlGuardError`, `FORBIDDEN_NODES`, `POSTGRES_DENY_FUNCTIONS`.
|
|
74
|
+
- `testing.StubReranker`; `testing_langfuse` (extra `langfuse`): `LangfuseRig`, `observed`,
|
|
75
|
+
`FakeLangfusePrompts`, `RefusingLangfuse`.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- `index_order.IndexOrderedTransport` moved out of `pydantic_ai_embedder` (still importable
|
|
80
|
+
there, the same object) and also orders `/rerank` replies' `results`.
|
|
81
|
+
|
|
82
|
+
## [0.10.0] - 2026-11-03
|
|
83
|
+
|
|
84
|
+
### Added
|
|
85
|
+
|
|
86
|
+
- `pydantic_evals_judge` (extra `evals`): `PydanticEvalsJudge(role, model=|model_for=)`,
|
|
87
|
+
`JUDGE_PROFILE`, `clean_reason`, `verdict_of`, `judge_inputs`.
|
|
88
|
+
- `judge_sampling`: `Seen`, `seen_for`, `schema_of`, `judge_input`, `reply_text`, `sample_key`,
|
|
89
|
+
`selected`, `rubric(role, seen, *, platform, mode, today, tz_label)`, `verdict_label`.
|
|
90
|
+
- `lettucedetect_grounder` (extra `lettucedetect`): `LettuceDetectGrounder`, `load_detector`,
|
|
91
|
+
`Detector`, `MODEL`, `MAX_CONTEXT_CHARS`, `WINDOW_REASON`.
|
|
92
|
+
- `grounder_http`: `HttpGrounder`, `GrounderUnavailable`, `payload`.
|
|
93
|
+
- `claim_refute`: `refute_slots`, `refute_prompt(pack, ...)`, `refute(generate, role, *, pack, ...)`,
|
|
94
|
+
`RefuteClaim`, `GenerateFn`.
|
|
95
|
+
- `json_schema`: `research_schema`, `RESEARCH_ANALYSIS`, `ANALYSIS_KEY`, `without_analysis`,
|
|
96
|
+
`research(generate, role, ...)`.
|
|
97
|
+
- `testing.StubDetector`.
|
|
98
|
+
|
|
99
|
+
## [0.9.0] - 2026-11-03
|
|
100
|
+
|
|
101
|
+
### Added
|
|
102
|
+
|
|
103
|
+
- `chunking`: `chunk_text`, `break_floor`, `max_chars_for_chunks`, `content_hash`,
|
|
104
|
+
`should_ingest`, `CHUNK_CHARS`, `CHUNK_OVERLAP`.
|
|
105
|
+
- `retrieval_metrics`: `first_rank`, `hit_at_k`, `reciprocal_rank`, `agree_at_k`, `mean`, `clears`.
|
|
106
|
+
- `reference`: `GauntletEvaluator`, `NumberLawGrounder`, `RepoPromptSource`, `NoopTracer`,
|
|
107
|
+
`NoopGeneration`.
|
|
108
|
+
- `pydantic_ai_replay`: `Recorder`, `replay`, `Recording`, `key`, `NotRecorded`.
|
|
109
|
+
- `testing.StubCiter`, `testing.StubPromptSource`; `ports.Seed` (also exported from the root).
|
|
110
|
+
- `call_graders.TEXT_ONLY_GRADERS`, `graders_for_inputs`.
|
|
111
|
+
|
|
112
|
+
### Changed
|
|
113
|
+
|
|
114
|
+
- `call.generate` drops `TEXT_ONLY_GRADERS` for a call with `images=` or the new `web=True`, so an
|
|
115
|
+
image or a web tier no longer fails `numbers_from_inputs` by construction. With no `trace` hook
|
|
116
|
+
the call observes into `tracer_trace(NoopTracer())`, the package's one null sink.
|
|
117
|
+
|
|
118
|
+
## [0.8.1] - 2026-11-03
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
|
|
122
|
+
- `IndexOrderedTransport` re-sorts an `/embeddings` reply's `data` by `index`. pydantic-ai drops
|
|
123
|
+
`index`, so a server answering out of order put every vector on its neighbour's row, which the
|
|
124
|
+
count check cannot see. `openai_compatible_embeddings` always wraps its transport with it.
|
|
125
|
+
|
|
126
|
+
## [0.8.0] - 2026-11-03
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
|
|
130
|
+
- `ports.Embedder`, the ninth port, and `Embedded(vectors, outcome, stamp, reason, input_tokens)`:
|
|
131
|
+
one vector per text in one named space; `input_tokens=None` when the wire reported no usage.
|
|
132
|
+
- `pydantic_ai_embedder.PydanticAIEmbedder` on pydantic-ai's `OpenAIEmbeddingModel`: count and
|
|
133
|
+
dimension checks (a short batch or a wrong dimension is `invalid`, never a partial result),
|
|
134
|
+
batching, an asymmetric query prefix, and the generator's never-raise outcome classification.
|
|
135
|
+
- `openai_compatible_embeddings(base_url, model_name, key, transport=)`.
|
|
136
|
+
- `testing.StubEmbedder`: deterministic hashed unit vectors.
|
|
137
|
+
|
|
138
|
+
## [0.7.0] - 2026-11-03
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- `content`: `ContentItem` (`prov:Entity`), `Activity` (`prov:Activity`), `DataClass` (rights
|
|
143
|
+
`public < owned < licensed < scraped`, `private`, `link_required`; `join` takes the most
|
|
144
|
+
restrictive), `CEILING` per audience, and `print_gate(item, audience) -> GateVerdict`: ok, or one
|
|
145
|
+
named refusal (`rights`, `unclaimed`, `uncited`, `unsourced`, `link`).
|
|
146
|
+
|
|
147
|
+
## [0.6.0] - 2026-10-31
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
|
|
151
|
+
- `slots.to_prose(text)`: context a digit-free grammar can read with no figure to reach for. Every
|
|
152
|
+
digit run is removed whole (`numbers.DIGITS`), every character outside `PROSE_CHARS` becomes a
|
|
153
|
+
space.
|
|
154
|
+
|
|
155
|
+
### Fixed
|
|
156
|
+
|
|
157
|
+
- `__version__` matches the package version (it said 0.3.0).
|
|
158
|
+
|
|
159
|
+
## [0.5.0] - 2026-10-31
|
|
160
|
+
|
|
161
|
+
### Added
|
|
162
|
+
|
|
163
|
+
- `call_graders.nullable_paths(model)`: the nullable leaf paths of a pydantic model.
|
|
164
|
+
|
|
165
|
+
### Changed
|
|
166
|
+
|
|
167
|
+
- `call_graders.schema_complete` reads a None on a nullable pydantic field as an answer; a None on
|
|
168
|
+
a required field still fails, and a dict value is graded as before.
|
|
169
|
+
|
|
170
|
+
## [0.4.0] - 2026-10-29
|
|
171
|
+
|
|
172
|
+
### Added
|
|
173
|
+
|
|
174
|
+
- `numbers.loose(text, allowed, words_ok)`: the number law in one function. Digit runs by value
|
|
175
|
+
not in `allowed`, then spelled numbers and number-word tails not in `words_ok`, in order and
|
|
176
|
+
named.
|
|
177
|
+
- `grounding.loose`, `slots.loose`; `grounding.belt` and `slots.belt` count `loose`.
|
|
178
|
+
|
|
179
|
+
### Changed
|
|
180
|
+
|
|
181
|
+
- `numbers.uncited` is `loose` sorted into the cited-spans shape (same results on every pinned case).
|
|
182
|
+
- `call_graders.numbers_from_inputs` scores the distinct figures of `loose`, so spelled numbers
|
|
183
|
+
count; the prompt's own words and a `%` license them.
|
|
184
|
+
|
|
185
|
+
## [0.3.0] - 2026-10-28
|
|
186
|
+
|
|
187
|
+
### Added
|
|
188
|
+
|
|
189
|
+
- `slots`: grounded prose by construction. `PROSE_CHARS`, `prose_pattern`, `slot_pattern` (the
|
|
190
|
+
digit-free grammar), `SLOT`, `ID`, `cited`, `fill`, `problems` (unknown slot, stray brace, cited
|
|
191
|
+
twice), `belt` over filled text.
|
|
192
|
+
- `numbers.canon` and `numbers.digits`: digit runs compare by value ("2.20" == "2.2").
|
|
193
|
+
|
|
194
|
+
### Changed
|
|
195
|
+
|
|
196
|
+
- `grounding.belt` compares digit runs by value.
|
|
197
|
+
|
|
198
|
+
## [0.2.0] - 2026-10-23
|
|
199
|
+
|
|
200
|
+
### Added
|
|
201
|
+
|
|
202
|
+
- `grounding`: `Problem(path, reason, detail="")`, `describe`, `retry_message`,
|
|
203
|
+
`belt(text, allowed, words_ok, *, strip_links)`, `allowed_numbers(texts)`.
|
|
204
|
+
- `failure_reason(exc, *, with_cause=False)`: an optional ` <- <cause reason>` tail, cut by the
|
|
205
|
+
same body-safe rule and within `REASON_MAX`.
|
|
206
|
+
|
|
207
|
+
## [0.1.1] - 2026-10-22
|
|
208
|
+
|
|
209
|
+
### Added
|
|
210
|
+
|
|
211
|
+
- `py.typed` (PEP 561).
|
|
212
|
+
- A release workflow: a `v*` tag builds the wheel and sdist from the tagged commit, refuses a
|
|
213
|
+
tag that does not match the package version, and attaches both to a GitHub release.
|
|
214
|
+
|
|
215
|
+
## [0.1.0] - 2026-10-22
|
|
216
|
+
|
|
217
|
+
### Added
|
|
218
|
+
|
|
219
|
+
- The ports (Protocols and frozen value types), roles over a consumer's tier table, prompt packs
|
|
220
|
+
named by content sha, per-call graders, `generate()` with named outcomes and `Hooks`, the
|
|
221
|
+
pydantic-ai `Generator` adapter, and test doubles.
|
|
222
|
+
|
|
223
|
+
[Unreleased]: https://github.com/mjdevaccount/llm-ports/compare/v0.12.0...HEAD
|
|
224
|
+
[0.12.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.11.0...v0.12.0
|
|
225
|
+
[0.11.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.10.0...v0.11.0
|
|
226
|
+
[0.10.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.9.0...v0.10.0
|
|
227
|
+
[0.9.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.8.1...v0.9.0
|
|
228
|
+
[0.8.1]: https://github.com/mjdevaccount/llm-ports/compare/v0.8.0...v0.8.1
|
|
229
|
+
[0.8.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.7.0...v0.8.0
|
|
230
|
+
[0.7.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.6.0...v0.7.0
|
|
231
|
+
[0.6.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.5.0...v0.6.0
|
|
232
|
+
[0.5.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.4.0...v0.5.0
|
|
233
|
+
[0.4.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.3.0...v0.4.0
|
|
234
|
+
[0.3.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.2.0...v0.3.0
|
|
235
|
+
[0.2.0]: https://github.com/mjdevaccount/llm-ports/compare/v0.1.1...v0.2.0
|
|
236
|
+
[0.1.1]: https://github.com/mjdevaccount/llm-ports/compare/v0.1.0...v0.1.1
|
|
237
|
+
[0.1.0]: https://github.com/mjdevaccount/llm-ports/releases/tag/v0.1.0
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
uv sync # the locked dev environment
|
|
7
|
+
uv run pre-commit install # the docstring-vocabulary hook, on every commit
|
|
8
|
+
uv run pytest -q --cov # the suite, with the coverage floor
|
|
9
|
+
uv run ruff check . && uv run ruff format --check .
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
CI (`.github/workflows/ci.yml`) runs the same commands on Python 3.12–3.14 and Windows, plus the
|
|
13
|
+
suite at every dependency's declared lower bound, `twine check --strict` on the build, and
|
|
14
|
+
`zizmor` over the workflows.
|
|
15
|
+
|
|
16
|
+
## Docstrings
|
|
17
|
+
|
|
18
|
+
A docstring states the contract. Its reader is someone calling the function, not someone
|
|
19
|
+
auditing why it was written.
|
|
20
|
+
|
|
21
|
+
1. **Contract only.** What it does, `Args:`, `Returns:`, `Raises:` (or that it never raises), in
|
|
22
|
+
the present tense, Google style. Rules that always hold go in an `Invariants:` section.
|
|
23
|
+
2. **No history and no deployment detail.** No incident dates, replay counts, host, service or
|
|
24
|
+
consumer names, model sizes, or ticket ids. A *compatibility fact* stays ("llama.cpp's grammar
|
|
25
|
+
compiler ignores `maxLength` beside `pattern`"). The *reason* for a design choice moves to an
|
|
26
|
+
ADR, and the docstring keeps a one-line pointer (`Rationale: ADR-0002.`).
|
|
27
|
+
3. **Emphasis by structure, not capitals.** An invariant goes in `Invariants:`; a warning goes in
|
|
28
|
+
its own sentence. Capitals are for constants and acronyms.
|
|
29
|
+
4. **Neutral examples.** Orders, documents, tickets, invoices.
|
|
30
|
+
|
|
31
|
+
`ruff` enforces the Google layout (`D` rules). A `pre-commit` hook (`.pre-commit-config.yaml`)
|
|
32
|
+
rejects deployment vocabulary in `llm_ports/`.
|
|
33
|
+
|
|
34
|
+
## Where history goes
|
|
35
|
+
|
|
36
|
+
- **What changed**: `CHANGELOG.md` ([Keep a Changelog](https://keepachangelog.com/en/1.1.0/)).
|
|
37
|
+
- **Why, with the evidence**: `docs/adr/NNNN-title.md` ([MADR](https://adr.github.io/madr/)).
|
|
38
|
+
Evidence is stated without naming the system it came from ("on a production summarizer, 40 of
|
|
39
|
+
64 replies invented a figure with the instruction in place").
|
|
40
|
+
|
|
41
|
+
## Releasing
|
|
42
|
+
|
|
43
|
+
Bump `version` in `pyproject.toml`, move `Unreleased` in `CHANGELOG.md` under the new version, and
|
|
44
|
+
merge. Then push a `vX.Y.Z` tag on `main`. `release.yml` runs the whole of `ci.yml`, checks the tag
|
|
45
|
+
against the version, builds, runs `twine check --strict`, then, independently of each other,
|
|
46
|
+
uploads to PyPI (trusted publishing through the `pypi` environment, with PEP 740 attestations) and
|
|
47
|
+
attaches the wheel and sdist to a GitHub release. The release assets never wait on the upload:
|
|
48
|
+
consumers that pin the wheel URL get it whatever PyPI did. A published file is never replaced.
|
|
49
|
+
|
|
50
|
+
To dry-run the pipeline, run `release.yml` from `main` by hand (`gh workflow run release.yml`). It
|
|
51
|
+
uploads a dev release, `X.Y.Z.devN` with N the run number, which installers take only when asked
|
|
52
|
+
(`pip install --pre`).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mjdevaccount
|
|
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.
|