pandion-api 0.1.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.
Files changed (87) hide show
  1. pandion_api-0.1.0/.gitignore +57 -0
  2. pandion_api-0.1.0/CHANGELOG.md +49 -0
  3. pandion_api-0.1.0/LICENSE +21 -0
  4. pandion_api-0.1.0/PKG-INFO +206 -0
  5. pandion_api-0.1.0/README.md +179 -0
  6. pandion_api-0.1.0/pyproject.toml +45 -0
  7. pandion_api-0.1.0/src/pandion_api/__init__.py +15 -0
  8. pandion_api-0.1.0/src/pandion_api/app.py +67 -0
  9. pandion_api-0.1.0/src/pandion_api/checks.py +92 -0
  10. pandion_api-0.1.0/src/pandion_api/credentials.py +58 -0
  11. pandion_api-0.1.0/src/pandion_api/deps.py +26 -0
  12. pandion_api-0.1.0/src/pandion_api/documents.py +206 -0
  13. pandion_api-0.1.0/src/pandion_api/errors.py +20 -0
  14. pandion_api-0.1.0/src/pandion_api/jobs.py +141 -0
  15. pandion_api-0.1.0/src/pandion_api/keycheck.py +68 -0
  16. pandion_api-0.1.0/src/pandion_api/limits.py +107 -0
  17. pandion_api-0.1.0/src/pandion_api/metrics.py +91 -0
  18. pandion_api-0.1.0/src/pandion_api/observability.py +200 -0
  19. pandion_api-0.1.0/src/pandion_api/pages.py +165 -0
  20. pandion_api-0.1.0/src/pandion_api/pipelines.py +108 -0
  21. pandion_api-0.1.0/src/pandion_api/policy.py +234 -0
  22. pandion_api-0.1.0/src/pandion_api/reports.py +116 -0
  23. pandion_api-0.1.0/src/pandion_api/routes/__init__.py +0 -0
  24. pandion_api-0.1.0/src/pandion_api/routes/ask.py +97 -0
  25. pandion_api-0.1.0/src/pandion_api/routes/chat.py +199 -0
  26. pandion_api-0.1.0/src/pandion_api/routes/conversations.py +72 -0
  27. pandion_api-0.1.0/src/pandion_api/routes/documents.py +221 -0
  28. pandion_api-0.1.0/src/pandion_api/routes/evaluations.py +330 -0
  29. pandion_api-0.1.0/src/pandion_api/routes/feedback.py +58 -0
  30. pandion_api-0.1.0/src/pandion_api/routes/health.py +15 -0
  31. pandion_api-0.1.0/src/pandion_api/routes/projects.py +213 -0
  32. pandion_api-0.1.0/src/pandion_api/routes/questions.py +486 -0
  33. pandion_api-0.1.0/src/pandion_api/routes/results.py +126 -0
  34. pandion_api-0.1.0/src/pandion_api/routes/runs.py +114 -0
  35. pandion_api-0.1.0/src/pandion_api/routes/serve_ask.py +71 -0
  36. pandion_api-0.1.0/src/pandion_api/routes/serve_health.py +46 -0
  37. pandion_api-0.1.0/src/pandion_api/routes/settings.py +47 -0
  38. pandion_api-0.1.0/src/pandion_api/routes/sort.py +38 -0
  39. pandion_api-0.1.0/src/pandion_api/routes/strategies.py +60 -0
  40. pandion_api-0.1.0/src/pandion_api/runs.py +323 -0
  41. pandion_api-0.1.0/src/pandion_api/serve.py +236 -0
  42. pandion_api-0.1.0/src/pandion_api/settings.py +27 -0
  43. pandion_api-0.1.0/src/pandion_api/version.py +3 -0
  44. pandion_api-0.1.0/tests/apihelpers.py +92 -0
  45. pandion_api-0.1.0/tests/conftest.py +11 -0
  46. pandion_api-0.1.0/tests/test_api_app.py +82 -0
  47. pandion_api-0.1.0/tests/test_api_ask.py +111 -0
  48. pandion_api-0.1.0/tests/test_api_boundary.py +163 -0
  49. pandion_api-0.1.0/tests/test_api_categories.py +112 -0
  50. pandion_api-0.1.0/tests/test_api_chat.py +309 -0
  51. pandion_api-0.1.0/tests/test_api_checks.py +65 -0
  52. pandion_api-0.1.0/tests/test_api_collection.py +65 -0
  53. pandion_api-0.1.0/tests/test_api_cost.py +181 -0
  54. pandion_api-0.1.0/tests/test_api_documents.py +162 -0
  55. pandion_api-0.1.0/tests/test_api_evaluations.py +566 -0
  56. pandion_api-0.1.0/tests/test_api_feedback.py +163 -0
  57. pandion_api-0.1.0/tests/test_api_generate_questions.py +231 -0
  58. pandion_api-0.1.0/tests/test_api_logs.py +93 -0
  59. pandion_api-0.1.0/tests/test_api_metrics.py +101 -0
  60. pandion_api-0.1.0/tests/test_api_metrics_port.py +91 -0
  61. pandion_api-0.1.0/tests/test_api_optional_imports.py +26 -0
  62. pandion_api-0.1.0/tests/test_api_otel.py +132 -0
  63. pandion_api-0.1.0/tests/test_api_packaging.py +130 -0
  64. pandion_api-0.1.0/tests/test_api_pages.py +128 -0
  65. pandion_api-0.1.0/tests/test_api_policy_serve.py +49 -0
  66. pandion_api-0.1.0/tests/test_api_projects.py +173 -0
  67. pandion_api-0.1.0/tests/test_api_questions.py +394 -0
  68. pandion_api-0.1.0/tests/test_api_results.py +141 -0
  69. pandion_api-0.1.0/tests/test_api_roles.py +51 -0
  70. pandion_api-0.1.0/tests/test_api_run_queue.py +247 -0
  71. pandion_api-0.1.0/tests/test_api_runs.py +346 -0
  72. pandion_api-0.1.0/tests/test_api_serve_app.py +96 -0
  73. pandion_api-0.1.0/tests/test_api_serve_ask.py +53 -0
  74. pandion_api-0.1.0/tests/test_api_serve_errors.py +116 -0
  75. pandion_api-0.1.0/tests/test_api_serve_exposure.py +52 -0
  76. pandion_api-0.1.0/tests/test_api_serve_keys.py +64 -0
  77. pandion_api-0.1.0/tests/test_api_serve_limits.py +118 -0
  78. pandion_api-0.1.0/tests/test_api_serve_memory_safety.py +54 -0
  79. pandion_api-0.1.0/tests/test_api_serve_policy_required.py +61 -0
  80. pandion_api-0.1.0/tests/test_api_serve_retention.py +96 -0
  81. pandion_api-0.1.0/tests/test_api_serve_store_url.py +21 -0
  82. pandion_api-0.1.0/tests/test_api_serve_user_data.py +116 -0
  83. pandion_api-0.1.0/tests/test_api_settings.py +102 -0
  84. pandion_api-0.1.0/tests/test_api_sort.py +152 -0
  85. pandion_api-0.1.0/tests/test_api_strategies.py +244 -0
  86. pandion_api-0.1.0/tests/test_api_trace_id.py +53 -0
  87. pandion_api-0.1.0/tests/test_api_url_documents.py +289 -0
@@ -0,0 +1,57 @@
1
+ .remember/
2
+
3
+ # Python
4
+ .venv/
5
+ __pycache__/
6
+ *.pyc
7
+ .pytest_cache/
8
+ .coverage
9
+ htmlcov/
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+
14
+ # Playground data — regenerable, sometimes large
15
+ artifacts/
16
+ sources/
17
+ runs/
18
+ graphs/
19
+ qa_sets/
20
+ embeddings/
21
+
22
+ # Secrets
23
+ .env
24
+ .env.*
25
+ !.env.example
26
+
27
+ # Node (phase 5)
28
+ node_modules/
29
+ playground/frontend/dist/
30
+
31
+ # Editors / OS
32
+ .vscode/
33
+ .idea/
34
+ .DS_Store
35
+ Thumbs.db
36
+
37
+ # Third-party agent skills (installed via `npx skills add`, not project source)
38
+ .agents/
39
+ .claude/skills/
40
+
41
+ # Browser-automation screenshots and traces
42
+ .playwright-mcp/
43
+
44
+ # Agent working notes, and the private plans, specs and design system (.superpowers/docs/)
45
+ .superpowers/
46
+ # Local launch-video output and the skill installer lock file
47
+ brag-output*/
48
+ skills-lock.json
49
+ # Agent worktrees
50
+ .claude/worktrees/
51
+
52
+ # The own-strategies example writes its index here
53
+ examples/pandion/own_strategies/work/
54
+ # The pandion command examples write their index here
55
+ examples/pandion/cli-index/
56
+ # The pandion-api example server keeps its files here
57
+ examples/pandion-api/pandion-data/
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ Notable changes, newest release first, commits oldest first within a release. Releases with only housekeeping commits (docs, chore, test, ci, refactor) are not listed. A commit that touches several parts appears in each part's changelog.
4
+
5
+ Generated by git-cliff from Conventional Commit titles. See CONTRIBUTING.md to regenerate it.
6
+
7
+ ## Unreleased
8
+
9
+ ### Added
10
+
11
+ - The REST package starts, with a Policy, and the playground becomes a pandion-api app (#111)
12
+ - Strategies, explain and pipeline validation, with a request's pipeline prepared safely (#112)
13
+ - Documents with the site's rules, and today's upload tests run on /api/v2 (#113)
14
+ - A document's file and pages, sharing the playground's pdfium lock; today's page tests run on /api/v2 (#114)
15
+ - Index runs and questions as runs, with progress, stop, keys and owners (#115)
16
+ - Results by id, clearing the cache, the miss trace and question sets (#116)
17
+ - Evaluations and comparisons as runs, and model key settings (#117)
18
+ - What the app and CLI need from Pandion: public extras, the pipeline file, and one-question comparisons (#119)
19
+ - Cap questions per run and runs per visitor on the demo (#135)
20
+ - Usage and cost in run JSON (#150)
21
+ - Cost of the varied step and after (#151)
22
+ - Write questions for a document as a run (#158)
23
+ - Read Word, PowerPoint, HTML, Markdown, text and images (#162)
24
+ - Llm_context adds a model-written context before embedding (#166)
25
+ - Filter a search by document, pages or section (#170)
26
+ - Add a document from a link, and non-PDF uploads (#173)
27
+ - Compare an evaluation with a baseline on the server (#178)
28
+ - Say which step and setting a refused pipeline is about (#179)
29
+ - Sum up a chunk set with pandion.chunk.stats (#180)
30
+ - Own-document scoring, totals by document, one set across documents (#184)
31
+ - One question set for a pipeline with several documents (#186)
32
+ - Compare recipes over several documents (#188)
33
+ - Categories, routes and sorting over REST (C6) (#215)
34
+ - Evaluate and compare name the index collection (T11) (#241)
35
+ - Stage and trace id on executor events (T17) (#243)
36
+ - Serve mode with streaming /chat, policy roles and limits (T15)
37
+ - Serve ask, conversations and memories routes (T15b)
38
+ - Json logs, trace id and otel spans for serve (T18)
39
+ - Fenced sources, unsourced links removed (T30a)
40
+ - Serve hardening, scrubbed errors, default limits (T29)
41
+ - Prometheus metrics on its own port (T19)
42
+ - Forget-user, sql url_env and retention sweep (T30b)
43
+ - POST /feedback and feedback_total metric (T26) (#265)
44
+
45
+ ### Fixed
46
+
47
+ - Find the gold passage with Pandion's own rule (#177)
48
+ - Refuse a PDF or image renamed as a text file before indexing (#191)
49
+ - A new question set replaces every older set it overlaps (#192)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nadeem Khan
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,206 @@
1
+ Metadata-Version: 2.5
2
+ Name: pandion-api
3
+ Version: 0.1.0
4
+ Summary: Pandion's public API over HTTP, with one Policy object for a site's rules
5
+ Project-URL: Documentation, https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md
6
+ Project-URL: Source, https://github.com/nadeem4/rag-playground/tree/main/packages/pandion-api
7
+ Project-URL: Issues, https://github.com/nadeem4/rag-playground/issues
8
+ Author: Nadeem Khan
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: fastapi,pandion,rag,rest,retrieval
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: <3.14,>=3.13
17
+ Requires-Dist: fastapi>=0.141.1
18
+ Requires-Dist: pandion[anthropic,openai,pdfium]<0.9,>=0.8.0
19
+ Requires-Dist: pillow>=12.3.0
20
+ Requires-Dist: python-multipart>=0.0.32
21
+ Provides-Extra: metrics
22
+ Requires-Dist: prometheus-client>=0.21; extra == 'metrics'
23
+ Provides-Extra: otel
24
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27; extra == 'otel'
25
+ Requires-Dist: opentelemetry-sdk>=1.27; extra == 'otel'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # pandion-api
29
+
30
+ pandion-api serves [Pandion](https://github.com/nadeem4/rag-playground/blob/main/packages/pandion/README.md) over REST. It is a FastAPI app factory:
31
+ `create_app` gives you a FastAPI app with Pandion's documents, runs, questions,
32
+ evaluations and comparisons as HTTP routes. RAG Playground mounts it at `/api/v2`.
33
+
34
+ Every route that takes a pipeline takes the pipeline file's shape as JSON, the same dict
35
+ `Pipeline.from_dict` reads. Everything a site wants to limit is one `Policy` object, and
36
+ the default allows everything.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install pandion-api
42
+ ```
43
+
44
+ It needs Python 3.13 and works with the Pandion versions its `pyproject.toml` declares
45
+ (`pandion>=0.8.0,<0.9` today), which `pip` installs for you. The pipeline's store needs its extra, for example
46
+ `pip install pandion-api "pandion[lancedb]"`.
47
+
48
+ [docs/pandion-api/index.md](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md) is the full reference.
49
+ [Changelog](https://github.com/nadeem4/rag-playground/blob/main/packages/pandion-api/CHANGELOG.md): what changed in each release.
50
+
51
+ ## Run it
52
+
53
+ ```python
54
+ # serve.py
55
+ from pathlib import Path
56
+
57
+ from pandion_api import ApiSettings, create_app
58
+
59
+ app = create_app(ApiSettings(data_dir=Path("./pandion-data"), prefix="/api/v2"))
60
+ ```
61
+
62
+ ```bash
63
+ uvicorn serve:app
64
+ ```
65
+
66
+ With no `prefix` the routes sit at the root, so once it runs, `curl http://127.0.0.1:8000/health`
67
+ (or `/api/v2/health` with the prefix above) is the first call. The reference has a
68
+ [step by step first run](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md#a-first-run-step-by-step).
69
+
70
+ `ApiSettings` takes `data_dir`, the one folder every request runs under. It also takes
71
+ `models` (a `pandion.ModelSettings`), `prefix` (where the routes are mounted) and `title`.
72
+ Pass a `Policy` as the second argument to `create_app` to set your site's rules.
73
+
74
+ [examples/pandion-api/](https://github.com/nadeem4/rag-playground/blob/main/examples/pandion-api) has a server script, a Python client
75
+ that validates, uploads, indexes, asks and evaluates, the same steps as curl commands,
76
+ and a small Policy.
77
+
78
+ ## Serve mode
79
+
80
+ For a site's own users, `create_app(settings, policy, mode="serve", pipeline="pipeline.yaml")`
81
+ loads one pipeline file once and answers `POST /chat`, as JSON or as server-sent events. Your
82
+ `Policy` says who is asking (`user_id`), which roles they have (`roles`) and whether they may
83
+ ask now (`limit`). Serve refuses to start without a Policy that overrides all three; pass
84
+ `Policy.anonymous()` to say on purpose that everyone is one user. Build the index first with
85
+ `pandion sync`.
86
+
87
+ Serve is hardened by default: generic errors with a trace id, model keys from the environment
88
+ only, public model endpoints only, a limit of 20 turns a minute and 2 active turns for each
89
+ user, a 64 KB request body, a 120 second turn, no `/docs` and no CORS. See
90
+ [Serve mode: hardened defaults](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#serve-mode-hardened-defaults).
91
+ [`serve_policy.py`](https://github.com/nadeem4/rag-playground/blob/main/examples/pandion-api/serve_policy.py) shows a Policy that checks a
92
+ signed gateway token.
93
+
94
+ ```bash
95
+ curl -N http://127.0.0.1:8000/chat -H 'content-type: application/json' \
96
+ -d '{"messages": [{"role": "user", "content": "How long is the notice period?"}], "stream": true}'
97
+ ```
98
+
99
+ Serve also has `POST /ask` (one question, nothing stored), `GET /conversations`,
100
+ `GET` and `DELETE /conversations/{id}`, `GET /memories` and `DELETE /memories/{id}`, and
101
+ `POST /feedback` (up or down on a trace). A user
102
+ only ever sees their own; anything else is a 404.
103
+
104
+ [docs/pandion-api/serve-mode.md](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#serve-mode) has the request, the events and `/ready`.
105
+
106
+ Serve deletes conversations older than `ServeSettings(retention_days=90)` every day, and
107
+ `pandion forget-user` removes one user's data. See [Your users' data](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#your-users-data).
108
+
109
+ Serve mode logs one JSON line per request, step and failure, and sets an `X-Trace-Id` header.
110
+ For OpenTelemetry spans, install `pandion-api[otel]` and set `OTEL_EXPORTER_OTLP_ENDPOINT`.
111
+ See [Logs and traces](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#logs-and-traces).
112
+ For Prometheus metrics on their own port, install `pandion-api[metrics]` and set
113
+ `ServeSettings(metrics_port=9464)`. See [Metrics](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#metrics).
114
+
115
+ ## The routes
116
+
117
+ Each path is under `prefix`.
118
+
119
+ | Route | What it does |
120
+ |---|---|
121
+ | `GET /health` | Says the server is up, with the Pandion and pandion-api versions. |
122
+ | `GET /strategies` | Lists every strategy by step, with its settings as a JSON schema. |
123
+ | `POST /explain` | Says in plain words what a strategy will do with given settings. |
124
+ | `POST /pipelines/validate` | Checks a pipeline and returns it as a YAML file, or the problems. |
125
+ | `GET /documents` | Lists the documents this caller may read. |
126
+ | `POST /documents` | Stores an uploaded file by its fingerprint. |
127
+ | `POST /documents/url` | Fetches one web page and stores it like an upload, with its link as `origin_url`. |
128
+ | `DELETE /documents/{id}` | Deletes a document, its record and its question set. |
129
+ | `GET /documents/{id}/file` | Returns the file's own bytes. |
130
+ | `GET /documents/{id}/pages` | Returns each page's size. |
131
+ | `GET /documents/{id}/pages/{n}` | Returns one page as a PNG image. |
132
+ | `GET /documents/{id}/pages/{n}/find` | Finds where a piece of text is on a page. |
133
+ | `POST /documents/{id}/questions` | Checks a question set against the document, and keeps it if the Policy says so. |
134
+ | `GET /documents/{id}/questions` | Returns the question set kept for a document. |
135
+ | `DELETE /documents/{id}/questions` | Removes that question set. |
136
+ | `POST /documents/{id}/questions/generate` | Has a model write test questions for a document, as a run. |
137
+ | `POST /questions` | Checks one question set against several documents, and keeps it if the Policy says so. |
138
+ | `GET /questions` | Returns the question sets kept against several documents, as one. |
139
+ | `POST /golds/find` | Says which piece first holds a gold passage, by the eval step's rule. |
140
+ | `POST /evaluations/against` | Compares a run with a baseline: questions newly missed and newly found, and which run is better. |
141
+ | `POST /pipelines/sort` | Shows which category each document would go to and why. Nothing runs. |
142
+ | `POST /projects/serve` | Writes the deployable serve project for a pipeline, its questions per category and where it will run, as a zip or (`?preview=1`) JSON. Sample questions are left out. |
143
+ | `POST /runs` | Starts a run that indexes documents, or stops after one step. |
144
+ | `GET /runs/{run_id}` | Returns a run's status, its events and its result. |
145
+ | `GET /runs/{run_id}/events` | Streams a run's events as server-sent events. |
146
+ | `POST /runs/{run_id}/cancel` | Stops a run between steps. |
147
+ | `POST /ask` | Searches the documents for a question, and answers it. |
148
+ | `POST /evaluations` | Checks whether each question's answer came back, and which step lost a miss. |
149
+ | `POST /comparisons` | Runs one step's variants side by side and names the best. |
150
+ | `GET /results/{id}` | Returns a stored result's type and details. |
151
+ | `GET /results/{id}/payload` | Returns the stored result itself. |
152
+ | `POST /chunk-stats` | Sums up several chunk sets, one chunker's over several documents, as one. |
153
+ | `DELETE /cache` | Removes every stored result and the embedding cache, and keeps the documents. |
154
+ | `GET /settings/models` | Says where the server's own key for each provider comes from, never the key. |
155
+ | `POST /settings/models/check` | Checks that the key a request would use works. |
156
+ | `POST /trace` | Follows one missed question step by step through a finished run. |
157
+
158
+ Every run's result carries `usage` on each step (the token counts and model ids of its
159
+ model call, or null) and `cost` on each report (seconds, tokens and estimated dollars).
160
+ [Speed and cost](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md#speed-and-cost) lists the fields.
161
+
162
+ ## The Policy
163
+
164
+ A site subclasses `pandion_api.Policy` and overrides only what it needs. A method refuses
165
+ by raising `fastapi.HTTPException`. These are the hooks, from `pandion_api/policy.py`:
166
+
167
+ - `dependencies()`: FastAPI dependencies to run on every route, such as one that sets a cookie.
168
+ - `can_read(sha, request)`: may this request read this document?
169
+ - `check_upload(file, request)`: read an upload and return its bytes, or refuse it.
170
+ - `check_document(data, filename, request)`: refuse a document's bytes, for an upload and a URL document alike.
171
+ - `check_fetch(url, request)`: refuse a link before it is fetched.
172
+ - `fetch_slot(request)`: a context manager the fetch runs inside, for example to limit fetches at once.
173
+ - `max_url_bytes(request)`: the largest page a link may fetch (20 MB by default).
174
+ - `on_uploaded(document, request)`: called right after an upload's record is written.
175
+ - `on_delete(sha, document, request)`: refuse a delete, or say whether to delete the file now.
176
+ - `describe(document, request)`: extra keys to add to a document in the list.
177
+ - `private(sha, request)`: is this document one caller's own, so its page images are cached privately?
178
+ - `keeps_question_sets(request)`: keep a checked question set, or only check it?
179
+ - `max_active_runs()`: how many runs may work at once.
180
+ - `strategies(catalog, request)`: the strategy list a caller is shown.
181
+ - `check_run(config, request)`: refuse a pipeline before it runs.
182
+ - `public_endpoints_only(request)`: accept only a public https address for a custom model endpoint?
183
+ - `max_variants(request)`: how many variants one comparison may run.
184
+ - `check_question_count(kind, count, request)`: refuse an evaluation or comparison with too many questions.
185
+ - `max_runs_per_owner(request)`: how many runs one owner may have at once.
186
+ - `run_owner(request)`: who a run belongs to.
187
+ - `check_clear_cache(request)`: refuse `DELETE /cache`.
188
+ - `server_key(provider)`: the key the server itself has for a provider, and where it came from.
189
+ - `key(provider, request)`: the key for this request, and where it came from.
190
+ - `credentials(request)`: the keys a run gets.
191
+ - `missing_key(provider)`: what a key check says when there is no key.
192
+ - `check_provider(provider, request)`: refuse a key check for a provider.
193
+ - `scrub(text)`: an error message as it may leave the server.
194
+ - `on_startup(app)` and `on_shutdown(app)`: called once as the server starts and stops.
195
+
196
+ A complete, tested example is [`policy_example.py`](https://github.com/nadeem4/rag-playground/blob/main/examples/pandion-api/policy_example.py): a `SizeLimitedPolicy` that changes only `check_document` to refuse a document over a size limit with a 413.
197
+
198
+ Two class attributes set wording: `not_readable` is the message for a document the caller
199
+ may not read, and `not_keeping_note` is what a question set report says when the set was
200
+ not kept.
201
+
202
+ ## How the playground uses it
203
+
204
+ The playground's backend, in [playground/backend/](https://github.com/nadeem4/rag-playground/blob/main/playground/backend/README.md),
205
+ calls `create_app` with `ApiSettings(prefix="/api/v2")` and `PlaygroundPolicy`, the hosted
206
+ demo's rules. It then adds its own routes under `/api` and serves the web app.
@@ -0,0 +1,179 @@
1
+ # pandion-api
2
+
3
+ pandion-api serves [Pandion](https://github.com/nadeem4/rag-playground/blob/main/packages/pandion/README.md) over REST. It is a FastAPI app factory:
4
+ `create_app` gives you a FastAPI app with Pandion's documents, runs, questions,
5
+ evaluations and comparisons as HTTP routes. RAG Playground mounts it at `/api/v2`.
6
+
7
+ Every route that takes a pipeline takes the pipeline file's shape as JSON, the same dict
8
+ `Pipeline.from_dict` reads. Everything a site wants to limit is one `Policy` object, and
9
+ the default allows everything.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install pandion-api
15
+ ```
16
+
17
+ It needs Python 3.13 and works with the Pandion versions its `pyproject.toml` declares
18
+ (`pandion>=0.8.0,<0.9` today), which `pip` installs for you. The pipeline's store needs its extra, for example
19
+ `pip install pandion-api "pandion[lancedb]"`.
20
+
21
+ [docs/pandion-api/index.md](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md) is the full reference.
22
+ [Changelog](https://github.com/nadeem4/rag-playground/blob/main/packages/pandion-api/CHANGELOG.md): what changed in each release.
23
+
24
+ ## Run it
25
+
26
+ ```python
27
+ # serve.py
28
+ from pathlib import Path
29
+
30
+ from pandion_api import ApiSettings, create_app
31
+
32
+ app = create_app(ApiSettings(data_dir=Path("./pandion-data"), prefix="/api/v2"))
33
+ ```
34
+
35
+ ```bash
36
+ uvicorn serve:app
37
+ ```
38
+
39
+ With no `prefix` the routes sit at the root, so once it runs, `curl http://127.0.0.1:8000/health`
40
+ (or `/api/v2/health` with the prefix above) is the first call. The reference has a
41
+ [step by step first run](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md#a-first-run-step-by-step).
42
+
43
+ `ApiSettings` takes `data_dir`, the one folder every request runs under. It also takes
44
+ `models` (a `pandion.ModelSettings`), `prefix` (where the routes are mounted) and `title`.
45
+ Pass a `Policy` as the second argument to `create_app` to set your site's rules.
46
+
47
+ [examples/pandion-api/](https://github.com/nadeem4/rag-playground/blob/main/examples/pandion-api) has a server script, a Python client
48
+ that validates, uploads, indexes, asks and evaluates, the same steps as curl commands,
49
+ and a small Policy.
50
+
51
+ ## Serve mode
52
+
53
+ For a site's own users, `create_app(settings, policy, mode="serve", pipeline="pipeline.yaml")`
54
+ loads one pipeline file once and answers `POST /chat`, as JSON or as server-sent events. Your
55
+ `Policy` says who is asking (`user_id`), which roles they have (`roles`) and whether they may
56
+ ask now (`limit`). Serve refuses to start without a Policy that overrides all three; pass
57
+ `Policy.anonymous()` to say on purpose that everyone is one user. Build the index first with
58
+ `pandion sync`.
59
+
60
+ Serve is hardened by default: generic errors with a trace id, model keys from the environment
61
+ only, public model endpoints only, a limit of 20 turns a minute and 2 active turns for each
62
+ user, a 64 KB request body, a 120 second turn, no `/docs` and no CORS. See
63
+ [Serve mode: hardened defaults](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#serve-mode-hardened-defaults).
64
+ [`serve_policy.py`](https://github.com/nadeem4/rag-playground/blob/main/examples/pandion-api/serve_policy.py) shows a Policy that checks a
65
+ signed gateway token.
66
+
67
+ ```bash
68
+ curl -N http://127.0.0.1:8000/chat -H 'content-type: application/json' \
69
+ -d '{"messages": [{"role": "user", "content": "How long is the notice period?"}], "stream": true}'
70
+ ```
71
+
72
+ Serve also has `POST /ask` (one question, nothing stored), `GET /conversations`,
73
+ `GET` and `DELETE /conversations/{id}`, `GET /memories` and `DELETE /memories/{id}`, and
74
+ `POST /feedback` (up or down on a trace). A user
75
+ only ever sees their own; anything else is a 404.
76
+
77
+ [docs/pandion-api/serve-mode.md](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#serve-mode) has the request, the events and `/ready`.
78
+
79
+ Serve deletes conversations older than `ServeSettings(retention_days=90)` every day, and
80
+ `pandion forget-user` removes one user's data. See [Your users' data](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#your-users-data).
81
+
82
+ Serve mode logs one JSON line per request, step and failure, and sets an `X-Trace-Id` header.
83
+ For OpenTelemetry spans, install `pandion-api[otel]` and set `OTEL_EXPORTER_OTLP_ENDPOINT`.
84
+ See [Logs and traces](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#logs-and-traces).
85
+ For Prometheus metrics on their own port, install `pandion-api[metrics]` and set
86
+ `ServeSettings(metrics_port=9464)`. See [Metrics](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/serve-mode.md#metrics).
87
+
88
+ ## The routes
89
+
90
+ Each path is under `prefix`.
91
+
92
+ | Route | What it does |
93
+ |---|---|
94
+ | `GET /health` | Says the server is up, with the Pandion and pandion-api versions. |
95
+ | `GET /strategies` | Lists every strategy by step, with its settings as a JSON schema. |
96
+ | `POST /explain` | Says in plain words what a strategy will do with given settings. |
97
+ | `POST /pipelines/validate` | Checks a pipeline and returns it as a YAML file, or the problems. |
98
+ | `GET /documents` | Lists the documents this caller may read. |
99
+ | `POST /documents` | Stores an uploaded file by its fingerprint. |
100
+ | `POST /documents/url` | Fetches one web page and stores it like an upload, with its link as `origin_url`. |
101
+ | `DELETE /documents/{id}` | Deletes a document, its record and its question set. |
102
+ | `GET /documents/{id}/file` | Returns the file's own bytes. |
103
+ | `GET /documents/{id}/pages` | Returns each page's size. |
104
+ | `GET /documents/{id}/pages/{n}` | Returns one page as a PNG image. |
105
+ | `GET /documents/{id}/pages/{n}/find` | Finds where a piece of text is on a page. |
106
+ | `POST /documents/{id}/questions` | Checks a question set against the document, and keeps it if the Policy says so. |
107
+ | `GET /documents/{id}/questions` | Returns the question set kept for a document. |
108
+ | `DELETE /documents/{id}/questions` | Removes that question set. |
109
+ | `POST /documents/{id}/questions/generate` | Has a model write test questions for a document, as a run. |
110
+ | `POST /questions` | Checks one question set against several documents, and keeps it if the Policy says so. |
111
+ | `GET /questions` | Returns the question sets kept against several documents, as one. |
112
+ | `POST /golds/find` | Says which piece first holds a gold passage, by the eval step's rule. |
113
+ | `POST /evaluations/against` | Compares a run with a baseline: questions newly missed and newly found, and which run is better. |
114
+ | `POST /pipelines/sort` | Shows which category each document would go to and why. Nothing runs. |
115
+ | `POST /projects/serve` | Writes the deployable serve project for a pipeline, its questions per category and where it will run, as a zip or (`?preview=1`) JSON. Sample questions are left out. |
116
+ | `POST /runs` | Starts a run that indexes documents, or stops after one step. |
117
+ | `GET /runs/{run_id}` | Returns a run's status, its events and its result. |
118
+ | `GET /runs/{run_id}/events` | Streams a run's events as server-sent events. |
119
+ | `POST /runs/{run_id}/cancel` | Stops a run between steps. |
120
+ | `POST /ask` | Searches the documents for a question, and answers it. |
121
+ | `POST /evaluations` | Checks whether each question's answer came back, and which step lost a miss. |
122
+ | `POST /comparisons` | Runs one step's variants side by side and names the best. |
123
+ | `GET /results/{id}` | Returns a stored result's type and details. |
124
+ | `GET /results/{id}/payload` | Returns the stored result itself. |
125
+ | `POST /chunk-stats` | Sums up several chunk sets, one chunker's over several documents, as one. |
126
+ | `DELETE /cache` | Removes every stored result and the embedding cache, and keeps the documents. |
127
+ | `GET /settings/models` | Says where the server's own key for each provider comes from, never the key. |
128
+ | `POST /settings/models/check` | Checks that the key a request would use works. |
129
+ | `POST /trace` | Follows one missed question step by step through a finished run. |
130
+
131
+ Every run's result carries `usage` on each step (the token counts and model ids of its
132
+ model call, or null) and `cost` on each report (seconds, tokens and estimated dollars).
133
+ [Speed and cost](https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md#speed-and-cost) lists the fields.
134
+
135
+ ## The Policy
136
+
137
+ A site subclasses `pandion_api.Policy` and overrides only what it needs. A method refuses
138
+ by raising `fastapi.HTTPException`. These are the hooks, from `pandion_api/policy.py`:
139
+
140
+ - `dependencies()`: FastAPI dependencies to run on every route, such as one that sets a cookie.
141
+ - `can_read(sha, request)`: may this request read this document?
142
+ - `check_upload(file, request)`: read an upload and return its bytes, or refuse it.
143
+ - `check_document(data, filename, request)`: refuse a document's bytes, for an upload and a URL document alike.
144
+ - `check_fetch(url, request)`: refuse a link before it is fetched.
145
+ - `fetch_slot(request)`: a context manager the fetch runs inside, for example to limit fetches at once.
146
+ - `max_url_bytes(request)`: the largest page a link may fetch (20 MB by default).
147
+ - `on_uploaded(document, request)`: called right after an upload's record is written.
148
+ - `on_delete(sha, document, request)`: refuse a delete, or say whether to delete the file now.
149
+ - `describe(document, request)`: extra keys to add to a document in the list.
150
+ - `private(sha, request)`: is this document one caller's own, so its page images are cached privately?
151
+ - `keeps_question_sets(request)`: keep a checked question set, or only check it?
152
+ - `max_active_runs()`: how many runs may work at once.
153
+ - `strategies(catalog, request)`: the strategy list a caller is shown.
154
+ - `check_run(config, request)`: refuse a pipeline before it runs.
155
+ - `public_endpoints_only(request)`: accept only a public https address for a custom model endpoint?
156
+ - `max_variants(request)`: how many variants one comparison may run.
157
+ - `check_question_count(kind, count, request)`: refuse an evaluation or comparison with too many questions.
158
+ - `max_runs_per_owner(request)`: how many runs one owner may have at once.
159
+ - `run_owner(request)`: who a run belongs to.
160
+ - `check_clear_cache(request)`: refuse `DELETE /cache`.
161
+ - `server_key(provider)`: the key the server itself has for a provider, and where it came from.
162
+ - `key(provider, request)`: the key for this request, and where it came from.
163
+ - `credentials(request)`: the keys a run gets.
164
+ - `missing_key(provider)`: what a key check says when there is no key.
165
+ - `check_provider(provider, request)`: refuse a key check for a provider.
166
+ - `scrub(text)`: an error message as it may leave the server.
167
+ - `on_startup(app)` and `on_shutdown(app)`: called once as the server starts and stops.
168
+
169
+ A complete, tested example is [`policy_example.py`](https://github.com/nadeem4/rag-playground/blob/main/examples/pandion-api/policy_example.py): a `SizeLimitedPolicy` that changes only `check_document` to refuse a document over a size limit with a 413.
170
+
171
+ Two class attributes set wording: `not_readable` is the message for a document the caller
172
+ may not read, and `not_keeping_note` is what a question set report says when the set was
173
+ not kept.
174
+
175
+ ## How the playground uses it
176
+
177
+ The playground's backend, in [playground/backend/](https://github.com/nadeem4/rag-playground/blob/main/playground/backend/README.md),
178
+ calls `create_app` with `ApiSettings(prefix="/api/v2")` and `PlaygroundPolicy`, the hosted
179
+ demo's rules. It then adds its own routes under `/api` and serves the web app.
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "pandion-api"
3
+ version = "0.1.0"
4
+ description = "Pandion's public API over HTTP, with one Policy object for a site's rules"
5
+ readme = "README.md"
6
+ authors = [{ name = "Nadeem Khan" }]
7
+ requires-python = ">=3.13,<3.14"
8
+ dependencies = [
9
+ # anthropic and openai: the key check (keycheck.py) calls each SDK.
10
+ "pandion[pdfium,anthropic,openai]>=0.8.0,<0.9",
11
+ "fastapi>=0.141.1",
12
+ "pillow>=12.3.0",
13
+ "python-multipart>=0.0.32",
14
+ ]
15
+ license = "MIT"
16
+ license-files = ["LICENSE"]
17
+ keywords = ["retrieval", "rag", "rest", "fastapi", "pandion"]
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ ]
24
+
25
+ [project.urls]
26
+ Documentation = "https://github.com/nadeem4/rag-playground/blob/main/docs/pandion-api/index.md"
27
+ Source = "https://github.com/nadeem4/rag-playground/tree/main/packages/pandion-api"
28
+ Issues = "https://github.com/nadeem4/rag-playground/issues"
29
+
30
+ [project.optional-dependencies]
31
+ # OpenTelemetry spans in serve mode (docs/pandion-api/serve-mode.md, "Logs and traces").
32
+ otel = ["opentelemetry-sdk>=1.27", "opentelemetry-exporter-otlp-proto-http>=1.27"]
33
+ # Prometheus metrics on their own port in serve mode (docs/pandion-api/serve-mode.md, "Metrics").
34
+ metrics = ["prometheus-client>=0.21"]
35
+
36
+
37
+ [tool.uv.sources]
38
+ pandion = { workspace = true }
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/pandion_api"]
@@ -0,0 +1,15 @@
1
+ """pandion-api: Pandion's public API over HTTP, with one Policy for a site's rules.
2
+
3
+ `create_app(ApiSettings(data_dir=...), policy)` gives a FastAPI app. Every
4
+ endpoint that takes a pipeline takes the pipeline file's shape as JSON.
5
+ `create_app(settings, policy, mode="serve", pipeline="pipeline.yaml")` instead
6
+ serves one fixed pipeline to a site's users. See docs/pandion-api/index.md.
7
+ """
8
+
9
+ from pandion_api.app import create_app
10
+ from pandion_api.policy import Policy
11
+ from pandion_api.serve import ServeSettings
12
+ from pandion_api.settings import ApiSettings
13
+ from pandion_api.version import VERSION as __version__
14
+
15
+ __all__ = ["ApiSettings", "Policy", "ServeSettings", "create_app", "__version__"]
@@ -0,0 +1,67 @@
1
+ """create_app: a FastAPI app over Pandion's public API, with a site's Policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import asynccontextmanager
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from fastapi import FastAPI
10
+
11
+ from pandion_api.documents import DocumentStore
12
+ from pandion_api.policy import Policy
13
+ from pandion_api.routes import ask, documents, evaluations, health, projects, questions, results, sort, strategies
14
+ from pandion_api.routes import runs as run_routes
15
+ from pandion_api.routes import settings as settings_routes
16
+ from pandion_api.runs import RunManager
17
+ from pandion_api.settings import ApiSettings
18
+
19
+ #: Every router, in the order docs/pandion-api/index.md lists them.
20
+ ROUTERS = (
21
+ health.router, strategies.router, documents.router, questions.router, run_routes.router, sort.router, ask.router,
22
+ evaluations.router, results.router, settings_routes.router, projects.router,
23
+ )
24
+
25
+
26
+ def create_app(
27
+ settings: ApiSettings,
28
+ policy: Policy | None = None,
29
+ *,
30
+ mode: str = "lab",
31
+ pipeline: str | Path | None = None,
32
+ conversations: Any = None,
33
+ serve: Any = None,
34
+ ) -> FastAPI:
35
+ """The app. `policy` holds the site's rules; the default allows everything.
36
+
37
+ `mode="lab"` (the default) takes a pipeline with each request. `mode="serve"`
38
+ loads the one pipeline file at `pipeline` and answers POST /chat; it keeps
39
+ conversations in `conversations` (a Pandion conversation store, else SQL in
40
+ the data folder) and takes its chat settings from `serve` (`ServeSettings`).
41
+ See docs/pandion-api/serve-mode.md, "Serve mode"."""
42
+ if mode == "serve":
43
+ from pandion_api.serve import create_serve_app
44
+
45
+ return create_serve_app(settings, policy, pipeline, conversations, serve)
46
+ if mode != "lab":
47
+ raise ValueError(f"mode must be 'lab' or 'serve', not '{mode}'.")
48
+ policy = policy or Policy()
49
+ manager = RunManager(limit=policy.max_active_runs)
50
+
51
+ @asynccontextmanager
52
+ async def lifespan(app: FastAPI):
53
+ await policy.on_startup(app)
54
+ try:
55
+ yield
56
+ finally:
57
+ await manager.shutdown()
58
+ await policy.on_shutdown(app)
59
+
60
+ app = FastAPI(title=settings.title, lifespan=lifespan)
61
+ app.state.settings = settings
62
+ app.state.policy = policy
63
+ app.state.runs = manager
64
+ app.state.documents = DocumentStore(settings.sources)
65
+ for router in ROUTERS:
66
+ app.include_router(router, prefix=settings.prefix, dependencies=policy.dependencies())
67
+ return app