agents: contract 0.2.0 β descriptors.json as the single source, capabilities(), acall()#602
agents: contract 0.2.0 β descriptors.json as the single source, capabilities(), acall()#602ShawnChen-Sirius wants to merge 3 commits into
Conversation
β¦, acall()
The model-visible tool surface (names, description text, argument schemas)
moves out of code into chdb/agents/descriptors.json, vendored byte-identical
in every binding:
- tool_specs(dialect='anthropic'|'openai'|'mcp') and capabilities() are
generated from it (module-level; ChDBTool.tool_specs delegates). Framework
adapters derive their schema declarations from the same file instead of
hand-copying descriptions that then drift between languages.
- CONTRACT_VERSION + capabilities() give downstream consumers a probe-able
version/feature surface (previously only __beta__ = True and the package
version to guess from).
- acall(): async tool dispatch via a worker thread (aquery's twin), so
async-first frameworks stop re-implementing thread-pool wrappers around
call().
Cross-binding drift fixes, now conformance-tested:
- max_bytes counts UTF-8 bytes of each row's compact JSON; the previous
ASCII-escaped character count put the truncation point up to ~2.6x earlier
than the TypeScript binding's UTF-16 count on non-ASCII rows.
- Non-numeric per-call caps (max_rows/limit) raise a typed
ChDBError(INVALID_ARGUMENT) instead of a bare ValueError; on the call()
envelope path a JSON null optional argument now means "omitted".
conformance/cases.jsonl gains a header record asserting the fixture's
contract_version against the binding, capability-gated cases
("requires": <feature> β dataframe_query gets its first fixture coverage),
and cases for the behaviors above.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
@chibugai, review it |
There was a problem hiding this comment.
Pull request overview
Aligns chdb.agents with a versioned βagent tool contractβ by making descriptors.json the single source of truth for the model-visible tool surface, adding a probeable capabilities/version API, and introducing async tool dispatch (acall) to reduce cross-binding drift and simplify downstream integrations.
Changes:
- Add contract machinery:
descriptors.jsonβ generatedtool_specs(dialect=...), plusCONTRACT_VERSIONandcapabilities(). - Add async tool dispatch via
ChDBTool.acall()and tighten numeric cap validation to raise typedINVALID_ARGUMENT. - Extend conformance fixture/runner to include a header with
contract_version, capability-gated cases, and new behavior cases (UTF-8max_bytes, null optional args on envelope path).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_agents_conformance.py | Updates conformance runner to support fixture headers and capability-gated cases; adds dataframe_query invocation. |
| tests/test_agents_chdbtool.py | Adds unit tests for dialect generation, contract version/capabilities, descriptor/dispatch parity, and acall(). |
| pyproject.toml | Ships agents/descriptors.json in package data. |
| chdb/agents/tool.py | Adds _int_arg typed validation, UTF-8 max_bytes counting, generated tool specs delegation, and acall(). |
| chdb/agents/descriptors.py | New module to load descriptors.json, generate dialect tool specs, and expose contract capabilities/version. |
| chdb/agents/descriptors.json | New single-source contract file defining tool names/descriptions/param schemas and contract_version. |
| chdb/agents/CONTRACT.md | Documents contract v0.2.0, descriptors.json as the single source, capabilities probing, async semantics, and normative max_bytes rules. |
| chdb/agents/conformance/README.md | Documents fixture header record, capability gating, and dataframe_query case format. |
| chdb/agents/conformance/cases.jsonl | Adds header record, new conformance cases for UTF-8 max_bytes, typed invalid caps, null optional args, and dataframe_query gating. |
| chdb/agents/init.py | Re-exports CONTRACT_VERSION, capabilities, tool_specs, and load_descriptors as part of the public surface. |
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@ShawnChen-Sirius Reviewed at 0dbd6dc β OCR deep pass (opus-4-8, deterministic ruleset + LLM agent,
Local verification: ran Copilot's two nits (lenient header detection in Scope note: this review covers this PR only, not the companion chdb-io/chdb-node#70. |
Review follow-ups on the contract-0.2.0 change:
- load_descriptors() returns a deep copy of the cached parse, so a caller
mutating the result can no longer corrupt what tool_specs()/capabilities()
generate for everyone else in-process; a missing/broken descriptors.json now
raises a diagnosable ChDBError instead of a raw IOError/JSONDecodeError.
- An unknown param type in descriptors.json fails loudly in _json_schema()
instead of rendering verbatim β a typo in the shared asset must not silently
degrade the model-visible argument schema.
- call() validates the arguments payload before dispatch: a non-object
(string/number/array) returns the {ok: false, INVALID_ARGUMENT} envelope
instead of raising from dict(arguments). The dispatch path now never throws
for caller mistakes (CONTRACT.md P4 spells this out; new conformance case
p4_malformed_arguments covers both bindings).
- The conformance runner enforces the fixture shape: the first record must be
the header, every later record must carry an "id" β a case that lost its id
fails the run instead of being silently reclassified as a second header.
Tests: 33 passed (was 31).
Co-Authored-By: Claude Fable 5 <[email protected]>
Two safety hardenings on the agent-tool base. The scanner is upstreamed from
the copy the mcp-clickhouse chDB integration had to vendor because the
library lacked it β once this ships in a release, that vendored copy gets
deleted and the integration consumes the library version.
- The file_allowlist gate now scans MASKED SQL β string literals and comments
blanked position-preserving, backtick/double-quote-wrapped function names
matched β against the table functions the running engine exposes
(system.table_functions snapshot, unioned with a static fallback), minus a
shared safe-by-construction set. Every non-safe call must carry a literal
source argument inside the allowlist; a computed argument is denied instead
of skipped. Previously a literal-only regex over raw SQL meant `file`(...),
file/**/(...), file(concat(...)), and any source function outside the five
hard-coded names (mysql(), executable(), ...) all bypassed the gate.
dataframe_query exempts only the Python() names it itself injects.
- A caller-provided session is now probed (SELECT getSetting('readonly')) and
must match the declared read_only mode, else construction fails with a
typed CONFIG_MISMATCH error. Previously the constructor ran SET readonly=2
on the caller's shared session β irreversible by design, so it silently
locked every other user of that session. Sessions the tool creates itself
keep the set-at-construction behavior.
- Constructor setup failures now close the session the tool itself created
before re-raising, instead of leaking it (the TypeScript binding already
did this; Python gains the symmetric guard).
Conformance: five new scanner cases (backtick bypass, comment-between-name-
and-paren, computed argument, string-literal false-positive, allowlisted path
still passing) and the runner substitutes {{fixtures}} inside lists so tool
configs can carry allowlist paths.
Co-Authored-By: Claude Fable 5 <[email protected]>
What
chdb.agentsand its TypeScript twin (chdb-io/chdb-nodeintegrations/agents) had started to drift: the model-visible tool descriptions existed in three hand-written copies (this package'stool_specs(), chdb-node's framework descriptor list, and the tool docstrings in the pending mcp-clickhouse chDB integration), the two bindings countedmax_bytesdifferently, and the only version signal a downstream integration could pin against was__beta__ = True. This PR turns those into contract machinery:chdb/agents/descriptors.jsonis now the single source of the model-visible surface (tool names, description text, argument schemas).tool_specs(dialect="anthropic" | "openai" | "mcp")andcapabilities()are generated from it (module-level;ChDBTool.tool_specs()delegates). The file is vendored byte-identical in chdb-node, whose zod schemas are generated from it too (companion PR linked below).CONTRACT_VERSION+capabilities()return{contract_version, tools, features}, giving downstream integrations a probeable version/feature surface instead of guessing from the package version. Feature keys:dataframe_query(Python-only),attachments,file_allowlist,max_execution_time,async,streaming(reserved, currently false).acall()β async tool dispatch via a worker thread (aquery's twin), so async-first frameworks (AutoGen, Pydantic AI) bind to it instead of each re-implementing a thread-pool wrapper aroundcall().Cross-binding drift fixes (behavior changes)
max_bytesnow counts UTF-8 bytes of each row's compact JSON. The previous ASCII-escaped character count put the truncation point up to ~2.6Γ earlier than chdb-node's UTF-16 count on non-ASCII rows β a model-visible split between bindings. CONTRACT.md P3 now names the normative measure.max_rows/limit, per-call or constructor) raises a typedChDBErrorwithtype="INVALID_ARGUMENT"instead of a bareValueError.call()envelope path, a JSONnulloptional argument now means "omitted" (models routinely send null for optional args); the direct method path stays strict.Conformance fixture
conformance/cases.jsonlgains:contract_versionβ each binding's runner asserts it equals its ownCONTRACT_VERSION, so a stale vendored fixture fails loudly;"requires": "<feature>"βdataframe_querygets its first fixture coverage (runs on Python, auto-skipped by the TypeScript runner);Tests
python -m pytest tests/test_agents_chdbtool.py tests/test_agents_conformance.pyβ 31 passed (21 conformance cases + unit tests for the dialects, version/descriptor parity,acall(), and argument validation).Companion chdb-node PR: chdb-io/chdb-node#70
π€ Generated with Claude Code
Note
Add contract 0.2.0 to chdb agents with
descriptors.json,capabilities(), andacall()CONTRACT_VERSION = "0.2.0"in descriptors.py, exported viachdb.agents.capabilities()returning{contract_version, tools, features}andtool_specs(dialect)supportinganthropic,openai, andmcpdialects with typedINVALID_ARGUMENTerrors for unknown dialects.ChDBTool.__init__to probe (not mutate) caller-provided sessions, raisingCONFIG_MISMATCHwhen readonly state mismatches, and snapshots engine table functions for precise file-allowlist gating._enforce_allowlistusing masked SQL scanning viafind_source_calls; non-literal source arguments and out-of-allowlist paths now raiseACCESS_DENIED;dataframe_queryis exempted via_permit_fns={'python'}.acall()as an async wrapper aroundcall(), andcall()now returns typed error envelopes for malformed/non-dict argument payloads instead of raising.CONFIG_MISMATCHif a caller-provided session is not in the expected readonly mode, which is a breaking change for callers that relied on silent mode coercion.Macroscope summarized 61684f8.