{"id":51763099,"url":"https://github.com/rafaelpierre/qvalidate","last_synced_at":"2026-07-19T16:04:10.217Z","repository":{"id":367313935,"uuid":"1280241718","full_name":"rafaelpierre/qvalidate","owner":"rafaelpierre","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-25T12:40:42.000Z","size":3063,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-25T14:05:08.423Z","etag":null,"topics":["agentic-ai","agents","kdb","kdb-q","kdbx","text2sql","tool-calls"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rafaelpierre.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-25T11:55:40.000Z","updated_at":"2026-06-25T12:40:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rafaelpierre/qvalidate","commit_stats":null,"previous_names":["rafaelpierre/qvalidate"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rafaelpierre/qvalidate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelpierre%2Fqvalidate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelpierre%2Fqvalidate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelpierre%2Fqvalidate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelpierre%2Fqvalidate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafaelpierre","download_url":"https://codeload.github.com/rafaelpierre/qvalidate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelpierre%2Fqvalidate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35657587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-19T02:00:06.923Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-ai","agents","kdb","kdb-q","kdbx","text2sql","tool-calls"],"created_at":"2026-07-19T16:04:09.487Z","updated_at":"2026-07-19T16:04:10.189Z","avatar_url":"https://github.com/rafaelpierre.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# qvalidate\n\n### Parse-time validation for LLM-generated **kdb/q** — built to be dropped straight into an agent's tool loop.\n\n[![PyPI](https://img.shields.io/badge/pip%20install-qvalidate-8b5cf6?style=for-the-badge\u0026logo=pypi\u0026logoColor=white)](#install)\n[![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?style=for-the-badge\u0026logo=python\u0026logoColor=white)](#install)\n[![Coverage](https://img.shields.io/badge/coverage-89%25-3fb950?style=for-the-badge\u0026logo=pytest\u0026logoColor=white)](#tests)\n[![uv](https://img.shields.io/badge/built%20with-uv-de5fe9?style=for-the-badge\u0026logo=astral\u0026logoColor=white)](https://docs.astral.sh/uv/)\n[![Ruff](https://img.shields.io/badge/lint-ruff-261230?style=for-the-badge\u0026logo=ruff\u0026logoColor=white)](https://docs.astral.sh/ruff/)\n[![License](https://img.shields.io/badge/license-Apache%202.0-0891b2?style=for-the-badge)](#license)\n\n\u003cbr/\u003e\n\n\u003cimg src=\"assets/hero.png\" alt=\"qvalidate — pass/fail in one call\" width=\"760\"/\u003e\n\n\u003c/div\u003e\n\n---\n\n## Why this exists\n\nAn agent turns *\"show me the last trade price for AAPL\"* into a full kdb/q\nquery. Before that string runs, **something has to decide whether it's even\nparseable** — and the usual options are bad:\n\n- **Run it and see.** A malformed query throws inside your live kdb+ session,\n  pollutes state, and burns a round-trip just to learn it had an unbalanced\n  brace.\n- **Ask another LLM.** Slow, non-deterministic, and it hallucinates errors that\n  block perfectly good queries.\n\n`qvalidate` is the missing third option: a **pure-Python, sub-millisecond,\nzero-dependency** gate that tells you *exactly* what `q` itself would reject at\nparse time — and hands back structured metadata the agent can reason over. The\nlexer, parser and scope analysis are a faithful port of the\n[kx-vscode](https://github.com/KxSystems/kx-vscode) language server's q core\n(Chevrotain multi-mode lexer + single-pass scope/assignment/namespace analysis),\nso its verdicts match the editor your kdb+ engineers already trust.\n\n\u003e **No kdb+ runtime. No network. No API key. No model.** Just `validate(str)`.\n\n---\n\n## Plug-and-play for agents\n\n`qvalidate` is designed to sit in exactly one place: **between the model's output\nand your execution layer.** Three ways teams wire it in.\n\n### 1 · Gate-and-self-correct loop\n\nCatch the failure, feed the *structured* diagnostic back to the model, let it\nfix its own query — all before a single byte reaches kdb+.\n\n\u003cdiv align=\"center\"\u003e\u003cimg src=\"assets/self-correct.png\" alt=\"The agentic self-correction loop\" width=\"760\"/\u003e\u003c/div\u003e\n\n### 2 · Tool-call validator\n\nEvery result is a typed [pydantic](https://docs.pydantic.dev) model, so\n`.model_dump_json()` gives you a JSON-ready payload you can return verbatim from\na tool / function call — and the model itself doubles as the schema for the\nLLM's tool definition. No glue code, no custom serializer.\n\n\u003cdiv align=\"center\"\u003e\u003cimg src=\"assets/metadata.png\" alt=\"Typed pydantic output\" width=\"760\"/\u003e\u003c/div\u003e\n\n```python\nfrom qvalidate import validate\n\ndef validate_q_tool(query: str) -\u003e str:\n    \"\"\"An MCP / function-calling tool the model can invoke directly.\"\"\"\n    return validate(query).model_dump_json()\n```\n\n### 3 · Static guardrail in a pipeline\n\nReject obviously-broken queries *before* they enter an expensive RAG / planning\nchain — `validate()` is fast enough to call on every candidate without thinking\nabout it.\n\n```python\ncandidates = [q for q in model_outputs if validate(q).valid]\n```\n\n---\n\n## What it flags\n\nOnly **parse-time failures** — the things `q` itself rejects when parsing:\n\n| Code | Meaning |\n|------|---------|\n| `UNBALANCED_PAREN` / `UNBALANCED_BRACKET` / `UNBALANCED_BRACE` | An opener with no matching closer |\n| `UNEXPECTED_CLOSE` | A closer with no matching opener |\n| `MISMATCHED_DELIMITER` | `)` closing a `[`, etc. |\n| `UNCLOSED_STRING` | Unterminated string literal |\n| `INVALID_ESCAPE` | Bad string escape (valid: `\\n \\r \\t \\\\ \\/ \\\"` and octal `\\100`–`\\377`) |\n| `LEX_ERROR` | A character that cannot be lexed |\n| `QSQL_MISSING_FROM` | A `select` / `exec` with no `from` clause |\n\n### What it deliberately does **not** flag\n\nTo avoid false positives that would needlessly block the agent:\n\n- Unknown identifiers / globals (we don't know the live session namespace).\n- Unknown table or column names (no schema information).\n- Anything stylistic (unused vars/params, deprecation, formatting).\n\n---\n\n## Install\n\n```bash\nuv add qvalidate          # or:  pip install qvalidate\n```\n\nOne runtime dependency ([pydantic](https://docs.pydantic.dev) v2). Python 3.9+.\n\n## Usage\n\n```python\nfrom qvalidate import validate\n\nr = validate(\"select px, sz from trades where sym=`AAPL\")\n\nr.valid                       # True\nr.diagnostics                 # []  (list[Diagnostic] otherwise)\nr.metadata.defined_symbols    # symbols the query assigns\nr.metadata.references         # identifiers the query uses   → ['px','sz','trades','sym']\nr.metadata.namespaces         # e.g. ['.util']\nr.metadata.sql                # [SqlBlock(op='select', table='trades',\n                              #           columns=['px','sz','sym'])]\n```\n\nEvery result is a fully-typed **pydantic** model — `ValidationResult`,\n`Diagnostic`, `QueryMetadata`, `SqlBlock` — so you get IDE autocomplete,\nvalidation, and serialisation for free:\n\n```python\nr.model_dump()                # → dict\nr.model_dump_json()           # → JSON string  (ideal tool-call output)\nValidationResult.model_validate_json(payload)   # ← parse straight back\n```\n\nLower-level building blocks are exported too: `tokenize(text)` and\n`Source.create(uri, text)`.\n\n---\n\n## Tests\n\nThis is a [uv](https://docs.astral.sh/uv/) project. Sync the environment and run\nthe suite (coverage is enforced at **≥80%** via `--cov-fail-under`):\n\n```bash\nuv sync --extra test\nuv run pytest\n```\n\nThe optional **oracle** suite (`tests/test_oracle.py`) cross-checks every corpus\nquery against a real q parser via `pykx` — the authoritative guard that we never\nreject a query q would accept. It is skipped automatically when `pykx` (and a\nkdb+ runtime) is unavailable:\n\n```bash\nuv sync --extra oracle\nuv run pytest tests/test_oracle.py\n```\n\n### Lint \u0026 type-check\n\n```bash\nuv run ruff check        # style + unused-import lint\nuv run ty check          # static type analysis\n```\n\n### Commit hooks\n\n[`prek`](https://prek.j178.dev) runs `ruff format` and `ty check` on the Python\nfiles staged in each commit (config in [`prek.toml`](prek.toml)) — using the\nproject's uv environment without ever syncing or installing:\n\n```bash\nprek install             # wire up the git pre-commit hook\nprek run --all-files     # run the hooks on demand\n```\n\n---\n\n## Performance\n\nValidation is a per-token `re.match` loop over a short string — sub-millisecond\nin pure Python. Reference resolution is dict-indexed (O(n)). If batch throughput\never matters, only `lexer.py` need be swapped for a Rust core (e.g. `logos` via\nPyO3) behind this same API.\n\n## License\n\n[Apache-2.0](LICENSE.md) (matching the ported kx-vscode sources).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelpierre%2Fqvalidate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafaelpierre%2Fqvalidate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelpierre%2Fqvalidate/lists"}