lpgcast 0.1.1__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.
Files changed (184) hide show
  1. lpgcast-0.1.1/.github/workflows/build-docs.yml +65 -0
  2. lpgcast-0.1.1/.github/workflows/pre-commit.yml +39 -0
  3. lpgcast-0.1.1/.github/workflows/pypi-publish.yml +45 -0
  4. lpgcast-0.1.1/.github/workflows/tests.yml +76 -0
  5. lpgcast-0.1.1/.gitignore +225 -0
  6. lpgcast-0.1.1/.pre-commit-config.yaml +58 -0
  7. lpgcast-0.1.1/CHANGELOG.md +67 -0
  8. lpgcast-0.1.1/LICENSE +201 -0
  9. lpgcast-0.1.1/PKG-INFO +127 -0
  10. lpgcast-0.1.1/README.md +96 -0
  11. lpgcast-0.1.1/docs/_build/gen_cli.py +63 -0
  12. lpgcast-0.1.1/docs/_build/gen_pages.py +67 -0
  13. lpgcast-0.1.1/docs/_build/gen_settings.py +53 -0
  14. lpgcast-0.1.1/docs/assets/favicon.ico +0 -0
  15. lpgcast-0.1.1/docs/assets/favicon.svg +19 -0
  16. lpgcast-0.1.1/docs/assets/logo.png +0 -0
  17. lpgcast-0.1.1/docs/assets/logo.svg +18 -0
  18. lpgcast-0.1.1/docs/concepts/groundings.md +41 -0
  19. lpgcast-0.1.1/docs/concepts/identities.md +40 -0
  20. lpgcast-0.1.1/docs/concepts/index.md +49 -0
  21. lpgcast-0.1.1/docs/getting_started/installation.md +57 -0
  22. lpgcast-0.1.1/docs/getting_started/quickstart.md +97 -0
  23. lpgcast-0.1.1/docs/guides/benchmark.md +82 -0
  24. lpgcast-0.1.1/docs/guides/database.md +57 -0
  25. lpgcast-0.1.1/docs/guides/replay.md +43 -0
  26. lpgcast-0.1.1/docs/guides/schema.md +54 -0
  27. lpgcast-0.1.1/docs/index.md +134 -0
  28. lpgcast-0.1.1/docs/reference/index.md +8 -0
  29. lpgcast-0.1.1/docs/reference/output.md +41 -0
  30. lpgcast-0.1.1/docs/reference/python/index.md +20 -0
  31. lpgcast-0.1.1/docs/stylesheets/extra.css +497 -0
  32. lpgcast-0.1.1/examples/connections/file-backend.yaml +4 -0
  33. lpgcast-0.1.1/examples/connections/neo4j.yaml +7 -0
  34. lpgcast-0.1.1/examples/tickets/README.md +35 -0
  35. lpgcast-0.1.1/examples/tickets/episodes.jsonl +5 -0
  36. lpgcast-0.1.1/examples/tickets/schema.yaml +92 -0
  37. lpgcast-0.1.1/lpgcast/__init__.py +77 -0
  38. lpgcast-0.1.1/lpgcast/bench/README.md +12 -0
  39. lpgcast-0.1.1/lpgcast/bench/__init__.py +2 -0
  40. lpgcast-0.1.1/lpgcast/bench/cases/SPLITS.yaml +38 -0
  41. lpgcast-0.1.1/lpgcast/bench/cases/direction/helpdesk/case.yaml +2 -0
  42. lpgcast-0.1.1/lpgcast/bench/cases/direction/helpdesk/episodes.jsonl +2 -0
  43. lpgcast-0.1.1/lpgcast/bench/cases/direction/helpdesk/expected.yaml +38 -0
  44. lpgcast-0.1.1/lpgcast/bench/cases/direction/lab/case.yaml +2 -0
  45. lpgcast-0.1.1/lpgcast/bench/cases/direction/lab/episodes.jsonl +2 -0
  46. lpgcast-0.1.1/lpgcast/bench/cases/direction/lab/expected.yaml +48 -0
  47. lpgcast-0.1.1/lpgcast/bench/cases/direction/logistics/case.yaml +2 -0
  48. lpgcast-0.1.1/lpgcast/bench/cases/direction/logistics/episodes.jsonl +2 -0
  49. lpgcast-0.1.1/lpgcast/bench/cases/direction/logistics/expected.yaml +41 -0
  50. lpgcast-0.1.1/lpgcast/bench/cases/direction/plant/case.yaml +2 -0
  51. lpgcast-0.1.1/lpgcast/bench/cases/direction/plant/episodes.jsonl +2 -0
  52. lpgcast-0.1.1/lpgcast/bench/cases/direction/plant/expected.yaml +38 -0
  53. lpgcast-0.1.1/lpgcast/bench/cases/distractors/helpdesk/case.yaml +6 -0
  54. lpgcast-0.1.1/lpgcast/bench/cases/distractors/helpdesk/episodes.jsonl +2 -0
  55. lpgcast-0.1.1/lpgcast/bench/cases/distractors/helpdesk/expected.yaml +50 -0
  56. lpgcast-0.1.1/lpgcast/bench/cases/distractors/lab/case.yaml +6 -0
  57. lpgcast-0.1.1/lpgcast/bench/cases/distractors/lab/episodes.jsonl +2 -0
  58. lpgcast-0.1.1/lpgcast/bench/cases/distractors/lab/expected.yaml +42 -0
  59. lpgcast-0.1.1/lpgcast/bench/cases/distractors/logistics/case.yaml +6 -0
  60. lpgcast-0.1.1/lpgcast/bench/cases/distractors/logistics/episodes.jsonl +2 -0
  61. lpgcast-0.1.1/lpgcast/bench/cases/distractors/logistics/expected.yaml +48 -0
  62. lpgcast-0.1.1/lpgcast/bench/cases/distractors/plant/case.yaml +6 -0
  63. lpgcast-0.1.1/lpgcast/bench/cases/distractors/plant/episodes.jsonl +2 -0
  64. lpgcast-0.1.1/lpgcast/bench/cases/distractors/plant/expected.yaml +41 -0
  65. lpgcast-0.1.1/lpgcast/bench/cases/identity/helpdesk/case.yaml +2 -0
  66. lpgcast-0.1.1/lpgcast/bench/cases/identity/helpdesk/episodes.jsonl +2 -0
  67. lpgcast-0.1.1/lpgcast/bench/cases/identity/helpdesk/expected.yaml +36 -0
  68. lpgcast-0.1.1/lpgcast/bench/cases/identity/lab/case.yaml +2 -0
  69. lpgcast-0.1.1/lpgcast/bench/cases/identity/lab/episodes.jsonl +2 -0
  70. lpgcast-0.1.1/lpgcast/bench/cases/identity/lab/expected.yaml +43 -0
  71. lpgcast-0.1.1/lpgcast/bench/cases/identity/logistics/case.yaml +2 -0
  72. lpgcast-0.1.1/lpgcast/bench/cases/identity/logistics/episodes.jsonl +2 -0
  73. lpgcast-0.1.1/lpgcast/bench/cases/identity/logistics/expected.yaml +39 -0
  74. lpgcast-0.1.1/lpgcast/bench/cases/identity/plant/case.yaml +2 -0
  75. lpgcast-0.1.1/lpgcast/bench/cases/identity/plant/episodes.jsonl +2 -0
  76. lpgcast-0.1.1/lpgcast/bench/cases/identity/plant/expected.yaml +32 -0
  77. lpgcast-0.1.1/lpgcast/bench/cases/injection/helpdesk/case.yaml +2 -0
  78. lpgcast-0.1.1/lpgcast/bench/cases/injection/helpdesk/episodes.jsonl +2 -0
  79. lpgcast-0.1.1/lpgcast/bench/cases/injection/helpdesk/expected.yaml +51 -0
  80. lpgcast-0.1.1/lpgcast/bench/cases/injection/lab/case.yaml +2 -0
  81. lpgcast-0.1.1/lpgcast/bench/cases/injection/lab/episodes.jsonl +2 -0
  82. lpgcast-0.1.1/lpgcast/bench/cases/injection/lab/expected.yaml +57 -0
  83. lpgcast-0.1.1/lpgcast/bench/cases/injection/logistics/case.yaml +2 -0
  84. lpgcast-0.1.1/lpgcast/bench/cases/injection/logistics/episodes.jsonl +2 -0
  85. lpgcast-0.1.1/lpgcast/bench/cases/injection/logistics/expected.yaml +67 -0
  86. lpgcast-0.1.1/lpgcast/bench/cases/injection/plant/case.yaml +2 -0
  87. lpgcast-0.1.1/lpgcast/bench/cases/injection/plant/episodes.jsonl +2 -0
  88. lpgcast-0.1.1/lpgcast/bench/cases/injection/plant/expected.yaml +49 -0
  89. lpgcast-0.1.1/lpgcast/bench/cases/mention/helpdesk/case.yaml +2 -0
  90. lpgcast-0.1.1/lpgcast/bench/cases/mention/helpdesk/episodes.jsonl +3 -0
  91. lpgcast-0.1.1/lpgcast/bench/cases/mention/helpdesk/expected.yaml +55 -0
  92. lpgcast-0.1.1/lpgcast/bench/cases/mention/lab/case.yaml +2 -0
  93. lpgcast-0.1.1/lpgcast/bench/cases/mention/lab/episodes.jsonl +3 -0
  94. lpgcast-0.1.1/lpgcast/bench/cases/mention/lab/expected.yaml +55 -0
  95. lpgcast-0.1.1/lpgcast/bench/cases/mention/logistics/case.yaml +2 -0
  96. lpgcast-0.1.1/lpgcast/bench/cases/mention/logistics/episodes.jsonl +3 -0
  97. lpgcast-0.1.1/lpgcast/bench/cases/mention/logistics/expected.yaml +55 -0
  98. lpgcast-0.1.1/lpgcast/bench/cases/mention/plant/case.yaml +2 -0
  99. lpgcast-0.1.1/lpgcast/bench/cases/mention/plant/episodes.jsonl +3 -0
  100. lpgcast-0.1.1/lpgcast/bench/cases/mention/plant/expected.yaml +55 -0
  101. lpgcast-0.1.1/lpgcast/bench/cases/recast/helpdesk/case.yaml +3 -0
  102. lpgcast-0.1.1/lpgcast/bench/cases/recast/helpdesk/episodes.jsonl +2 -0
  103. lpgcast-0.1.1/lpgcast/bench/cases/recast/helpdesk/expected.yaml +56 -0
  104. lpgcast-0.1.1/lpgcast/bench/cases/recast/lab/case.yaml +3 -0
  105. lpgcast-0.1.1/lpgcast/bench/cases/recast/lab/episodes.jsonl +2 -0
  106. lpgcast-0.1.1/lpgcast/bench/cases/recast/lab/expected.yaml +55 -0
  107. lpgcast-0.1.1/lpgcast/bench/cases/recast/logistics/case.yaml +3 -0
  108. lpgcast-0.1.1/lpgcast/bench/cases/recast/logistics/episodes.jsonl +2 -0
  109. lpgcast-0.1.1/lpgcast/bench/cases/recast/logistics/expected.yaml +60 -0
  110. lpgcast-0.1.1/lpgcast/bench/cases/recast/plant/case.yaml +3 -0
  111. lpgcast-0.1.1/lpgcast/bench/cases/recast/plant/episodes.jsonl +2 -0
  112. lpgcast-0.1.1/lpgcast/bench/cases/recast/plant/expected.yaml +46 -0
  113. lpgcast-0.1.1/lpgcast/bench/cases/units/helpdesk/case.yaml +2 -0
  114. lpgcast-0.1.1/lpgcast/bench/cases/units/helpdesk/episodes.jsonl +2 -0
  115. lpgcast-0.1.1/lpgcast/bench/cases/units/helpdesk/expected.yaml +35 -0
  116. lpgcast-0.1.1/lpgcast/bench/cases/units/lab/case.yaml +2 -0
  117. lpgcast-0.1.1/lpgcast/bench/cases/units/lab/episodes.jsonl +3 -0
  118. lpgcast-0.1.1/lpgcast/bench/cases/units/lab/expected.yaml +58 -0
  119. lpgcast-0.1.1/lpgcast/bench/cases/units/logistics/case.yaml +2 -0
  120. lpgcast-0.1.1/lpgcast/bench/cases/units/logistics/episodes.jsonl +2 -0
  121. lpgcast-0.1.1/lpgcast/bench/cases/units/logistics/expected.yaml +40 -0
  122. lpgcast-0.1.1/lpgcast/bench/cases/units/plant/case.yaml +2 -0
  123. lpgcast-0.1.1/lpgcast/bench/cases/units/plant/episodes.jsonl +3 -0
  124. lpgcast-0.1.1/lpgcast/bench/cases/units/plant/expected.yaml +55 -0
  125. lpgcast-0.1.1/lpgcast/bench/cli.py +129 -0
  126. lpgcast-0.1.1/lpgcast/bench/corpus.py +274 -0
  127. lpgcast-0.1.1/lpgcast/bench/oracle.py +102 -0
  128. lpgcast-0.1.1/lpgcast/bench/report.py +314 -0
  129. lpgcast-0.1.1/lpgcast/bench/run.py +277 -0
  130. lpgcast-0.1.1/lpgcast/bench/schemas/helpdesk-distractors.yaml +102 -0
  131. lpgcast-0.1.1/lpgcast/bench/schemas/helpdesk.yaml +104 -0
  132. lpgcast-0.1.1/lpgcast/bench/schemas/lab-distractors.yaml +104 -0
  133. lpgcast-0.1.1/lpgcast/bench/schemas/lab.yaml +108 -0
  134. lpgcast-0.1.1/lpgcast/bench/schemas/logistics-distractors.yaml +106 -0
  135. lpgcast-0.1.1/lpgcast/bench/schemas/logistics.yaml +108 -0
  136. lpgcast-0.1.1/lpgcast/bench/schemas/plant-distractors.yaml +102 -0
  137. lpgcast-0.1.1/lpgcast/bench/schemas/plant.yaml +96 -0
  138. lpgcast-0.1.1/lpgcast/bench/score.py +525 -0
  139. lpgcast-0.1.1/lpgcast/cast.py +420 -0
  140. lpgcast-0.1.1/lpgcast/cli.py +224 -0
  141. lpgcast-0.1.1/lpgcast/config.py +154 -0
  142. lpgcast-0.1.1/lpgcast/episode.py +80 -0
  143. lpgcast-0.1.1/lpgcast/extract.py +147 -0
  144. lpgcast-0.1.1/lpgcast/ground.py +102 -0
  145. lpgcast-0.1.1/lpgcast/llm/__init__.py +29 -0
  146. lpgcast-0.1.1/lpgcast/llm/cache.py +175 -0
  147. lpgcast-0.1.1/lpgcast/llm/client.py +464 -0
  148. lpgcast-0.1.1/lpgcast/llm/jsonparse.py +260 -0
  149. lpgcast-0.1.1/lpgcast/llm/usage.py +150 -0
  150. lpgcast-0.1.1/lpgcast/output.py +63 -0
  151. lpgcast-0.1.1/lpgcast/prompt.py +103 -0
  152. lpgcast-0.1.1/lpgcast/repair.py +79 -0
  153. lpgcast-0.1.1/lpgcast/resolve.py +257 -0
  154. lpgcast-0.1.1/lpgcast/schema_io.py +39 -0
  155. lpgcast-0.1.1/lpgcast/select.py +469 -0
  156. lpgcast-0.1.1/lpgcast/sink.py +81 -0
  157. lpgcast-0.1.1/lpgcast/validate.py +415 -0
  158. lpgcast-0.1.1/lpgcast/write.py +199 -0
  159. lpgcast-0.1.1/properdocs.yml +120 -0
  160. lpgcast-0.1.1/pyproject.toml +84 -0
  161. lpgcast-0.1.1/test/conftest.py +191 -0
  162. lpgcast-0.1.1/test/data/episodes.jsonl +3 -0
  163. lpgcast-0.1.1/test/data/manifest.yaml +14 -0
  164. lpgcast-0.1.1/test/data/plant.yaml +68 -0
  165. lpgcast-0.1.1/test/data/schema.yaml +41 -0
  166. lpgcast-0.1.1/test/test_bench.py +303 -0
  167. lpgcast-0.1.1/test/test_cache.py +66 -0
  168. lpgcast-0.1.1/test/test_cast.py +251 -0
  169. lpgcast-0.1.1/test/test_cli.py +150 -0
  170. lpgcast-0.1.1/test/test_client.py +251 -0
  171. lpgcast-0.1.1/test/test_config.py +40 -0
  172. lpgcast-0.1.1/test/test_dependency_weight.py +85 -0
  173. lpgcast-0.1.1/test/test_episode.py +37 -0
  174. lpgcast-0.1.1/test/test_examples.py +18 -0
  175. lpgcast-0.1.1/test/test_extract.py +140 -0
  176. lpgcast-0.1.1/test/test_ground.py +39 -0
  177. lpgcast-0.1.1/test/test_jsonparse.py +71 -0
  178. lpgcast-0.1.1/test/test_prompt.py +81 -0
  179. lpgcast-0.1.1/test/test_repair.py +57 -0
  180. lpgcast-0.1.1/test/test_resolve.py +160 -0
  181. lpgcast-0.1.1/test/test_select.py +198 -0
  182. lpgcast-0.1.1/test/test_validate.py +238 -0
  183. lpgcast-0.1.1/test/test_write.py +181 -0
  184. lpgcast-0.1.1/uv.lock +2959 -0
@@ -0,0 +1,65 @@
1
+ name: Build GitHub Pages
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ paths:
7
+ - 'docs/**'
8
+ - 'properdocs.yml'
9
+ - 'pyproject.toml'
10
+ - 'uv.lock'
11
+ - 'lpgcast/**'
12
+ # A pull request builds the site strictly but does not deploy it.
13
+ pull_request:
14
+ paths:
15
+ - 'docs/**'
16
+ - 'properdocs.yml'
17
+ - 'pyproject.toml'
18
+ - 'uv.lock'
19
+ - 'lpgcast/**'
20
+ workflow_dispatch:
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ # One deployment at a time; a newer push queues instead of cancelling one in flight.
26
+ # Each pull request builds in its own group.
27
+ concurrency:
28
+ group: pages-${{ github.event_name == 'pull_request' && github.ref || 'deploy' }}
29
+ cancel-in-progress: false
30
+
31
+ jobs:
32
+ build:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: Checkout code
36
+ uses: actions/checkout@v7
37
+ - name: Install uv
38
+ uses: astral-sh/setup-uv@v10.2.0
39
+ with:
40
+ version: "0.12.21"
41
+ enable-cache: true
42
+ - name: Install dependencies
43
+ run: uv sync --no-sources --group docs
44
+ - name: Build site
45
+ run: uv run --no-sources properdocs build --strict --site-dir _site
46
+ - name: Upload Pages artifact
47
+ if: github.event_name != 'pull_request'
48
+ uses: actions/upload-pages-artifact@v5
49
+ with:
50
+ path: _site
51
+
52
+ deploy:
53
+ needs: build
54
+ if: github.event_name != 'pull_request'
55
+ permissions:
56
+ pages: write
57
+ id-token: write
58
+ environment:
59
+ name: github-pages
60
+ url: ${{ steps.deployment.outputs.page_url }}
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - name: Deploy to GitHub Pages
64
+ id: deployment
65
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,39 @@
1
+ name: pre-commit
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ pre-commit:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v10.2.0
22
+ with:
23
+ version: "0.12.21"
24
+ enable-cache: true
25
+ # Hook environments live in ~/.cache/pre-commit, outside uv's cache;
26
+ # keyed on the config so a pin change rebuilds and nothing else does.
27
+ - name: Cache hook environments
28
+ uses: actions/cache@v6
29
+ with:
30
+ path: ~/.cache/pre-commit
31
+ key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
32
+ # Lint and formatting only; type checking runs in tests.yml, where the
33
+ # whole program is installed.
34
+ - name: Install dependencies
35
+ run: uv sync --no-sources --group dev --no-install-project
36
+ - name: Run pre-commit
37
+ run: uv run --no-sources pre-commit run --all-files
38
+ env:
39
+ SKIP: ty
@@ -0,0 +1,45 @@
1
+ name: Upload Python Package to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ pypi-publish:
12
+ # Off until the `lpgcast` name is reserved on PyPI: set the repository
13
+ # variable PYPI_PUBLISH to `true` to turn publishing on.
14
+ if: vars.PYPI_PUBLISH == 'true'
15
+ runs-on: ubuntu-latest
16
+ environment:
17
+ name: pypi
18
+ url: https://pypi.org/p/lpgcast
19
+ permissions:
20
+ id-token: write
21
+
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v10.2.0
27
+ with:
28
+ version: "0.12.21"
29
+ # A release whose tag differs from the project version fails instead
30
+ # of publishing a version nobody tagged.
31
+ - name: Verify tag matches pyproject version
32
+ run: |
33
+ project_version="$(uv version --short)"
34
+ tag_version="${GITHUB_REF_NAME#v}"
35
+ echo "pyproject: ${project_version} / tag: ${tag_version}"
36
+ if [ "${project_version}" != "${tag_version}" ]; then
37
+ echo "::error::Release tag ${GITHUB_REF_NAME} does not match pyproject version ${project_version}"
38
+ exit 1
39
+ fi
40
+ - name: Build and publish
41
+ run: |
42
+ uv build --no-sources
43
+ uv publish
44
+ env:
45
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,76 @@
1
+ name: tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ # Development uses graflo from a sibling checkout (`tool.uv.sources`); CI has
16
+ # none, so every sync passes `--no-sources` and resolves graflo from PyPI.
17
+
18
+ jobs:
19
+ # Guards the packaged artifact: a plain install must import and stay light.
20
+ import-smoke:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v10.2.0
26
+ with:
27
+ version: "0.12.21"
28
+ - name: Build wheel
29
+ run: uv build --wheel --no-sources --out-dir dist
30
+ - name: Install the wheel
31
+ run: |
32
+ uv venv --python 3.12 /tmp/base-install
33
+ VIRTUAL_ENV=/tmp/base-install uv pip install dist/*.whl
34
+ - name: Import the documented entry points, loading nothing heavy
35
+ run: |
36
+ /tmp/base-install/bin/python -c "
37
+ import sys
38
+ from lpgcast import LPGCaster, load_schema, read_episodes, write_cast
39
+ forbidden = ['langchain', 'langgraph', 'torch', 'numpy', 'pandas', 'rdflib']
40
+ leaked = [name for name in forbidden if name in sys.modules]
41
+ assert not leaked, f'imported on a plain install: {leaked}'
42
+ print('import OK')
43
+ "
44
+ - name: Console scripts resolve
45
+ run: |
46
+ /tmp/base-install/bin/lpgcast --help
47
+ /tmp/base-install/bin/lpgcast-bench --help
48
+ /tmp/base-install/bin/lpgcast sheet --schema examples/tickets/schema.yaml > /dev/null
49
+
50
+ unit:
51
+ runs-on: ubuntu-latest
52
+ strategy:
53
+ fail-fast: false
54
+ matrix:
55
+ # PRs and non-main pushes: 3.12 only. Pushes to main also run 3.11 and 3.13.
56
+ python-version: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && fromJSON('["3.11", "3.12", "3.13"]') || fromJSON('["3.12"]') }}
57
+ env:
58
+ UV_PYTHON: ${{ matrix.python-version }}
59
+ steps:
60
+ - uses: actions/checkout@v7
61
+ - name: Install uv
62
+ uses: astral-sh/setup-uv@v10.2.0
63
+ with:
64
+ version: "0.12.21"
65
+ enable-cache: true
66
+ - name: Install dependencies
67
+ run: uv sync --no-sources --group dev
68
+ - name: Type check
69
+ if: matrix.python-version == '3.12'
70
+ run: uv run --no-sources ty check lpgcast test
71
+ # No model is called: tests use a scripted endpoint, and the `llm` and
72
+ # `neo4j` markers are skipped without --run-llm / --run-neo4j.
73
+ - name: Run tests
74
+ run: uv run --no-sources pytest
75
+ env:
76
+ PYTHONUNBUFFERED: '1'
@@ -0,0 +1,225 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ *.lcov
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # PyBuilder
73
+ .pybuilder/
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ # For a library or package, you might want to ignore these files since the code is
85
+ # intended to run in multiple environments; otherwise, check them in:
86
+ # .python-version
87
+
88
+ # pipenv
89
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
91
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
92
+ # install all needed dependencies.
93
+ # Pipfile.lock
94
+
95
+ # uv
96
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
97
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
98
+ # commonly ignored for libraries.
99
+ # uv.lock
100
+
101
+ # poetry
102
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
104
+ # commonly ignored for libraries.
105
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106
+ # poetry.lock
107
+ # poetry.toml
108
+
109
+ # pdm
110
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
111
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
112
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
113
+ # pdm.lock
114
+ # pdm.toml
115
+ .pdm-python
116
+ .pdm-build/
117
+
118
+ # pixi
119
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
120
+ # pixi.lock
121
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
122
+ # in the .venv directory. It is recommended not to include this directory in version control.
123
+ .pixi/*
124
+ !.pixi/config.toml
125
+
126
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
127
+ __pypackages__/
128
+
129
+ # Celery stuff
130
+ celerybeat-schedule*
131
+ celerybeat.pid
132
+
133
+ # Redis
134
+ *.rdb
135
+ *.aof
136
+ *.pid
137
+
138
+ # RabbitMQ
139
+ mnesia/
140
+ rabbitmq/
141
+ rabbitmq-data/
142
+
143
+ # ActiveMQ
144
+ activemq-data/
145
+
146
+ # SageMath parsed files
147
+ *.sage.py
148
+
149
+ # Environments
150
+ .env
151
+ .envrc
152
+ .venv
153
+ env/
154
+ venv/
155
+ ENV/
156
+ env.bak/
157
+ venv.bak/
158
+
159
+ # Spyder project settings
160
+ .spyderproject
161
+ .spyproject
162
+
163
+ # Rope project settings
164
+ .ropeproject
165
+
166
+ # mkdocs/Zensical documentation
167
+ /site
168
+
169
+ # mypy
170
+ .mypy_cache/
171
+ .dmypy.json
172
+ dmypy.json
173
+
174
+ # Pyre type checker
175
+ .pyre/
176
+
177
+ # pytype static type analyzer
178
+ .pytype/
179
+
180
+ # Cython debug symbols
181
+ cython_debug/
182
+
183
+ # PyCharm
184
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
185
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
186
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
187
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
188
+ # .idea/
189
+
190
+ # Abstra
191
+ # Abstra is an AI-powered process automation framework.
192
+ # Ignore directories containing user credentials, local state, and settings.
193
+ # Learn more at https://abstra.io/docs
194
+ .abstra/
195
+
196
+ # Visual Studio Code
197
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
198
+ # can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
199
+ # and can be added to the global gitignore or merged into this file. However, if you prefer, you
200
+ # could uncomment the following to ignore the entire vscode folder
201
+ # .vscode/
202
+ # Temporary file for partial code execution
203
+ tempCodeRunnerFile.py
204
+
205
+ # Ruff stuff:
206
+ .ruff_cache/
207
+
208
+ # PyPI configuration file
209
+ .pypirc
210
+
211
+ # Marimo
212
+ marimo/_static/
213
+ marimo/_lsp/
214
+ __marimo__/
215
+
216
+ # Streamlit
217
+ .streamlit/secrets.toml
218
+
219
+ # Response cache written by tests run without LPGCAST_CACHE_DIR
220
+ .test_cache/
221
+
222
+ # Benchmark reports; results are published elsewhere, never committed
223
+ bench-results/
224
+
225
+ .idea/
@@ -0,0 +1,58 @@
1
+ repos:
2
+ # Repository hygiene and JSON formatting. Env files, fixture data and vendored
3
+ # font files are excluded: their bytes are kept as committed.
4
+ - repo: https://github.com/pre-commit/pre-commit-hooks
5
+ rev: v6.0.0
6
+ hooks:
7
+ - id: check-merge-conflict
8
+ - id: check-added-large-files
9
+ args: [--maxkb=2000]
10
+ - id: end-of-file-fixer
11
+ exclude: (^|/)(\.env|data/)|^lpgcast/plot/fonts/|\.svg$
12
+ - id: trailing-whitespace
13
+ args: [--markdown-linebreak-ext=md]
14
+ exclude: (^|/)(\.env|data/)|^lpgcast/plot/fonts/|\.svg$
15
+ - id: pretty-format-json
16
+ args: [--autofix, --indent, '4', --no-sort-keys]
17
+
18
+ # Python linting and formatting
19
+ - repo: https://github.com/astral-sh/ruff-pre-commit
20
+ rev: v0.16.10
21
+ hooks:
22
+ - id: ruff-check
23
+ args: [--fix]
24
+ - id: ruff-format
25
+
26
+ - repo: local
27
+ hooks:
28
+ # Type checking
29
+ - id: ty
30
+ name: ty check
31
+ entry: uv run --frozen ty check lpgcast test
32
+ language: system
33
+ types: [python]
34
+ pass_filenames: false
35
+
36
+ # Package docs must not point outside this repository
37
+ - id: no-external-doc-paths
38
+ name: no external doc paths
39
+ language: pygrep
40
+ entry: '(?i)docs/(design|reports|ISSUES|ROADMAP|PLANNING)|benchmarking/|papers/|platform-demos/|\bworkspace\b'
41
+ files: \.md$
42
+
43
+ # YAML formatting
44
+ - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
45
+ rev: v2.16.0
46
+ hooks:
47
+ - id: pretty-format-yaml
48
+ args: [--autofix, --indent, '4', --preserve-quotes]
49
+
50
+ # TOML formatting and sorting. One tool only: pretty-format-toml is built on the
51
+ # toml-sort library, and its hook environment pins whatever toml-sort was current
52
+ # when it was installed. Once that differs from the rev below, the two rewrite
53
+ # inline tables back and forth ({a = 1} vs { a = 1 }) on every run.
54
+ - repo: https://github.com/pappasam/toml-sort
55
+ rev: v0.25.0
56
+ hooks:
57
+ - id: toml-sort
58
+ args: [-ia]
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.1] - 2026-10-11
9
+
10
+ ### Added
11
+
12
+ - **Package skeleton**: `lpgcast` on Python ≥ 3.11 with six runtime dependencies (`graflo`, `openai`,
13
+ `pydantic`, `pydantic-settings`, `click`, `pyyaml`).
14
+ - **Settings** from `LPGCAST_LLM_*`, `LPGCAST_CACHE_*` and `LPGCAST_*` variables only; no `.env` file
15
+ is read. The default model is `gpt-5.6-luna`.
16
+ - **`LLMClient.acomplete_json`**: one JSON object per request from any OpenAI-compatible endpoint;
17
+ strict `json_schema` output with a `json_object` fallback, tolerant parsing, re-ask on malformed
18
+ JSON, one re-issue on timeout, typed configuration errors.
19
+ - **Response cache** keyed on the logical request, written atomically; read-only mode and
20
+ `LPGCAST_CACHE_REQUIRE_HIT` for replays that make no request. Without an API key only cached
21
+ responses are served.
22
+ - **Usage accounting**: calls, failures and tokens, with billed tokens kept apart from tokens
23
+ replayed from the cache; `track_usage()` scopes it per task.
24
+ - **CLI** `lpgcast` (alias `text2lpg`) with `sheet`: the type sheet, or with `--json-schema` the
25
+ reply schema, for a schema or manifest file.
26
+ - **Episodes** (`Episode`, `read_episodes`): text, source id, reference time, metadata (never shown
27
+ to the model) and thread context, from JSON Lines.
28
+ - **Schema selection** (`select`): exact-form, phrase and number-unit lanes fused by rank, a relative
29
+ relevance gate, and closure by graflo's `subschema()` under a budget; a schema under budget passes
30
+ whole, and a large schema nothing matches is a `SelectionError` with a reason.
31
+ - **Extraction prompt** (`build_messages`): byte-deterministic, type sheet and rules first, the text
32
+ inside a tagged block it cannot close.
33
+ - **`extract`**: one call per episode in the flat reply format, parsed into wire models; an episode
34
+ over `LPGCAST_MAX_EPISODE_TOKENS` is refused before any call.
35
+ - **CLI** `lpgcast select`: the slice each text would be cast against, as JSON lines or type sheets.
36
+ - **Validation** (`validate`): unknown types and relations, properties not declared on their type,
37
+ values that do not coerce to their field type, insufficient identities, dangling refs and quotes
38
+ not found in the text are findings; values are coerced (declared units stripped), reversed edges
39
+ flipped and quotes matched ignoring case, accents and whitespace, each recorded as a fix.
40
+ - **Typed repair**: at most two follow-up requests listing the findings by kind; the attempt that
41
+ keeps the most items wins.
42
+ - **Quarantine sink** (`JsonlQuarantineSink`): left-out items, failed episodes and `unmapped`
43
+ entries, with the raw reply.
44
+ - **Identity resolution and assembly** (`resolve`): natural keys, digests for hash and funnel types,
45
+ UUIDs derived from schema, episode, type and ref for assigned and blank types; mentions are never
46
+ written as vertices, and an edge to a mention known only by a secondary identity waits for a lookup.
47
+ - **Groundings**: one per vertex, edge and property, with character offsets found from the quote.
48
+ - **`LPGCaster`**: the whole pipeline over many episodes, concurrently; a misconfigured model or a
49
+ cache miss under replay stops the run.
50
+ - **`write_cast`**: defines the schema on the target (reusing an existing one), looks up
51
+ secondary-identity mentions and upserts through graflo's writer.
52
+ - **CLI** `lpgcast cast`: graph, groundings, quarantine, episode results and a run manifest under
53
+ `--out`; `--db` writes to a graflo connection; `--replay` makes no request.
54
+ - **`LPGCaster.cast_episode`** returns an `EpisodeOutcome` that also carries the slice, the
55
+ first reply as checked and the attempt kept.
56
+ - **Benchmark** `lpgcast-bench`: a versioned corpus of seven families in four domains with a
57
+ dev/test split and a pinned digest; exact scoring with no model as judge; an oracle row; a
58
+ fresh-call re-cast; JSON and Markdown reports with token counts and optional prices.
59
+ - **Top-level API**: `from lpgcast import LPGCaster, load_schema, read_episodes, write_cast, ...`;
60
+ names load on first use, so `import lpgcast` stays light.
61
+ - **Documentation site**: installation, quick start, how a cast works, identities and mentions,
62
+ groundings and quarantine, guides to schemas, replay, databases and the benchmark, and a
63
+ reference whose settings, command-line and Python pages are generated from the code.
64
+ - **Examples**: `examples/tickets/` (a support-desk schema and five texts) and
65
+ `examples/connections/` (file backend and Neo4j).
66
+ - **CI**: tests (with a check that a plain install imports and stays light), pre-commit, docs to
67
+ GitHub Pages, and PyPI publishing on release once enabled.