Add Vercel AI SDK and Mastra tool adapters (chdb/ai-sdk, chdb/mastra)#61
Conversation
|
@chibugai, review it |
There was a problem hiding this comment.
Pull request overview
Adds first-class “agent tool” integrations for running ClickHouse SQL via chDB in two ESM-only TypeScript agent ecosystems (Vercel AI SDK and Mastra), sharing a single framework-agnostic executor to keep behavior consistent.
Changes:
- Introduces a shared
runChdbQuery()core (description + execution semantics) and two thin adapters (chdb/ai-sdk,chdb/mastra). - Exposes the adapters via new subpath exports and adds optional peer deps (
ai,@mastra/core,zod). - Adds a Vitest integration test covering the AI SDK adapter end-to-end.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/v3/integrations/ai-sdk.test.ts | Adds an end-to-end test for the Vercel AI SDK tool wrapper. |
| integrations/chdb-tool-core.mjs | Introduces shared tool description and runChdbQuery() executor used by both adapters. |
| integrations/ai-sdk.mjs | Adds the Vercel AI SDK tool() adapter wrapper. |
| integrations/ai-sdk.d.mts | Adds type declarations for the AI SDK adapter subpath. |
| integrations/mastra.mjs | Adds the Mastra createTool() adapter wrapper. |
| integrations/mastra.d.mts | Adds type declarations for the Mastra adapter subpath. |
| package.json | Adds ./ai-sdk and ./mastra exports, files whitelist entry, and optional peer/dev deps. |
| package-lock.json | Updates lockfile for new deps and version metadata. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Reviewed — OCR deep pass (opus-4-8, 5 source files) plus a manual read of the core and both adapters. No correctness or safety issues found; looks good to me. What I specifically checked and was happy with:
One non-blocking design note, just for awareness: the tool deliberately hands the model the full SQL surface, including table functions that reach external hosts and embed credentials ( |
b930240 to
5320f33
Compare
|
@chibugai, review it again |
- chdb-vector: import randomUUID from node:crypto (require() is undefined in ESM, so upsert without ids would have thrown); drop the require() helper. - chdb-vector: give __chdb_vector_meta a version column and use ReplacingMergeTree(_updated) so re-creating an index resolves deterministically under FINAL; populate _updated on insert. - chdb-vector: normalize topK with Math.max(1, Math.floor(...)) instead of `| 0` (bitwise wraps/negatives are invalid for the UInt32 bind). - chdb-tool-core: coerce maxRows to a positive integer once so 0/negative/NaN can't make slice()/`truncated` misbehave. - ai-sdk/mastra .d.mts: type the tools' execute() results (ChdbQueryResult / ChdbListTablesResult / ChdbDescribeResult) instead of returning unknown. - tests: use @ts-ignore (not @ts-expect-error) for the .mjs imports so a future resolution change can't fail the suite with an unused-directive error; add a ChDBVector case that upserts without ids (exercises randomUUID). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chdb-vector:
- import randomUUID from node:crypto (require() is undefined in ESM, so upsert
without ids would have thrown); drop the require() helper.
- idempotent upsert: the vector table is now ReplacingMergeTree(_v) ORDER BY id
and reads use FINAL, so re-inserting an id resolves to the latest row — replaces
the async, non-atomic lightweight-DELETE + INSERT (which could leave duplicates
or lose data on a crash). The vector_similarity ANN index is still used under
FINAL (verified via EXPLAIN indexes=1).
- __chdb_vector_meta gains a version column (ReplacingMergeTree(_updated)) so a
re-created index resolves deterministically; _updated populated on insert.
- normalize topK with Math.max(1, Math.floor(...)) instead of `| 0`.
- filterToSql escapes backslashes as well as quotes (ClickHouse interprets
backslash escapes — otherwise a metadata value could corrupt the SQL).
chdb-store:
- esc() neutralizes backslashes and quotes and maps null/undefined to an empty
literal (was quote-only — a backslash could break out of the literal).
- updateSpan merges only `args.updates` (UpdateSpanArgs = {traceId, spanId,
updates}), so extraneous top-level keys never leak into the persisted span.
chdb-tool-core:
- coerce maxRows to a positive integer once so 0/negative/NaN can't make
slice()/`truncated` misbehave.
ai-sdk/mastra .d.mts: type the tools' execute() results (ChdbQueryResult /
ChdbListTablesResult / ChdbDescribeResult) instead of returning unknown.
tests: use @ts-ignore (not @ts-expect-error) for the .mjs imports; add a
ChDBVector case that upserts without ids (exercises randomUUID).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a7fcf99 to
fde921f
Compare
…ore) A schema-aware agent integration for the two main TypeScript agent frameworks, over one shared, framework-agnostic executor core so behavior is identical. Tools (chdb/ai-sdk, chdb/mastra) — chdbTools() returns three tools so an agent can discover then query, instead of guessing SQL against an unknown schema: - chdbListTables — tables/views in the session - chdbDescribeSource — columns/types of a table or any table function (s3()/file()/postgresql()/…), via ClickHouse DESCRIBE TABLE - chdbQuery — read-only by default: reads run on a dedicated engine-level read-only session (SET readonly = 1) bound to the same data path, so a write/DDL the model emits is rejected by the engine, not just by a prompt. allowWrite opts out. Each returns rows/columns as JSON (capped at maxRows) and hands engine errors back to the model instead of throwing. chdbQueryTool() remains for the single-tool case. ChDBVector (chdb/mastra) — a Mastra vector store backed by chDB-core's HNSW vector index (vector_similarity), so similarity search is index-accelerated ANN, not a brute-force distance scan (verified via EXPLAIN indexes=1). Implements the full MastraVector contract (createIndex/upsert/query/describeIndex/listIndexes/ deleteIndex/updateVector/deleteVector/deleteVectors); metrics cosine + euclidean; true upsert (delete-then-insert); flat metadata equality filters. Authored as ESM (.mjs + .d.mts) since both frameworks are ESM-only. ai, @mastra/core, and zod are optional peer dependencies. Tests: 14 integration tests (toolset incl. read-only enforcement; ChDBVector ANN / filter / upsert-replace / describe / list / delete). Full v3 suite green (382 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes chDB a Mastra storage backend, scoped to the two domains where ClickHouse's columnar engine is the right tool, exported from chdb/mastra alongside chdbTools and ChDBVector: - memory — agent threads + messages - observability — AI tracing spans, so trace/span data (latencies, errors, span types, per-resource volume) is analytically queryable with plain SQL over the mastra_ai_spans table — the reason to back agent telemetry with ClickHouse. Storage model: each record is a full JSON blob plus indexed key columns (resourceId, threadId, spanType, startedAt, isRoot, …) in a ReplacingMergeTree keyed by id / (traceId, spanId) and versioned by an _updated column. This gives upsert-by-id semantics (the point updates ClickHouse otherwise handles awkwardly) and exact round-tripping of Mastra's nested record shapes; reads use FINAL so a re-saved id returns its latest version immediately. Implements MemoryStorage's required methods (save/get/update/delete thread, save/list/listById/update messages, listThreads with resourceId + metadata filters) and the ObservabilityStorage tracing path (createSpan, batchCreateSpans, updateSpan, getSpan, getRootSpan, getTrace, getSpans, listTraces with status + pagination). Other Mastra domains (workflows, scores, …) are intentionally left to the base — they are point-update/OLTP-shaped; compose another backend for them. Tests: 7 ChDBStore cases (thread/message round-trips + filters; span/trace read-write; updateSpan merge; listTraces status; an analytical GROUP BY over spans). Full v3 suite green (390 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chdb-vector:
- import randomUUID from node:crypto (require() is undefined in ESM, so upsert
without ids would have thrown); drop the require() helper.
- idempotent upsert: the vector table is now ReplacingMergeTree(_v) ORDER BY id
and reads use FINAL, so re-inserting an id resolves to the latest row — replaces
the async, non-atomic lightweight-DELETE + INSERT (which could leave duplicates
or lose data on a crash). The vector_similarity ANN index is still used under
FINAL (verified via EXPLAIN indexes=1).
- __chdb_vector_meta gains a version column (ReplacingMergeTree(_updated)) so a
re-created index resolves deterministically; _updated populated on insert.
- normalize topK with Math.max(1, Math.floor(...)) instead of `| 0`.
- filterToSql escapes backslashes as well as quotes (ClickHouse interprets
backslash escapes — otherwise a metadata value could corrupt the SQL).
chdb-store:
- esc() neutralizes backslashes and quotes and maps null/undefined to an empty
literal (was quote-only — a backslash could break out of the literal).
- updateSpan merges only `args.updates` (UpdateSpanArgs = {traceId, spanId,
updates}), so extraneous top-level keys never leak into the persisted span.
chdb-tool-core:
- coerce maxRows to a positive integer once so 0/negative/NaN can't make
slice()/`truncated` misbehave.
ai-sdk/mastra .d.mts: type the tools' execute() results (ChdbQueryResult /
ChdbListTablesResult / ChdbDescribeResult) instead of returning unknown.
tests: use @ts-ignore (not @ts-expect-error) for the .mjs imports; add a
ChDBVector case that upserts without ids (exercises randomUUID).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
readonly=1 rejects external table functions (file(), url(), s3()) with Code:164 READONLY, which breaks the tool's primary use case of querying external data. readonly=2 allows SELECT plus external table functions while still rejecting all writes/DDL, and cannot be escaped (lowering readonly is rejected by the engine, and INSERTs smuggled into multi-statement queries are rejected). Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
23a7441 to
d71344a
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- vector_similarity index used GRANULARITY 1, which builds one HNSW per 8192-row granule and prunes nothing, so ANN queries degraded to a full scan. Use a large GRANULARITY so one graph covers the whole part and granules are actually pruned (results are approximate ANN). - Metadata filters interpolated values into SQL and rendered numbers and booleans unquoted, so JSONExtractString(...) = 2024 threw Code 386 and the destructive deleteVectors path carried unbound values. Bind every filter value as a String param and compare against the JSON string form, which fixes numeric/boolean filters and keeps values out of the SQL text. - upsert accepted wrong-length vectors, silently corrupting the HNSW graph. Add CONSTRAINT check_dim CHECK length(vector) = <dim> so the engine rejects a dimension mismatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough pass, and for reproducing each of these on a real build. All four are fixed in 1. GRANULARITY. You're right, and this is the important one. 2. Numeric/boolean filters. Fixed. 3. Param binding. Filter values are now bound as 4. Dimension guard. Added Added tests for the numeric/boolean filter, delete-by-filter, and the dimension rejection ( |
…w integrations Additive minor release on top of 3.1.0 (2026-06-24). No breaking changes; existing 3.1.0 APIs continue to work unchanged. libchdb bits are unchanged (chdb-core v26.5.1-rc.1 packaged as @chdb/lib-* 26.5.2 — no republish needed, the subpackage-matrix job will idempotent-skip). ## What's new ### Layer 3 fluent query builder SDK (#55, #59) First-class typed fluent query API — Kysely-/Drizzle-style: const db = connect({ session }) const rows = await db .from('users') .where(r => r.age.gt(18)) .select('id', 'name') .execute() - `src/layer3/{builder,execute,compile,dialect,introspection,connect,types,codegen}/` - `chdb-gen-types` CLI — generates row types from a schema (Drizzle / Prisma / raw SQL sources) - `.stream()` terminal with server-side parameter binding - Arrow C Data Interface support: register JS columnar data as `arrowstream()` tables - 12 new test files under `test/v3/layer3/` ### Cross-language agent-tool contract + 2 framework adapters (#61, #66) - `chdb/ai-sdk` — Vercel AI SDK tools (`generateText`/`streamText`) - `chdb/mastra` — Mastra tools + `ChDBVector` (HNSW RAG store) + `ChDBStore` - Both are thin wrappers over `ChDBTool.call()` from `integrations/agents/`, which implements the cross-language contract vendored from `chdb-io/chdb` (`chdb/agents/CONTRACT.md`, chdb-io/chdb#597). - 7 canonical tools: `run_select_query` / `list_databases` / `list_tables` / `describe_table` / `get_sample_data` / `list_functions` / `attach_file` - 5 pillars enforced: engine-level readonly=2 (P1), value-vs-identifier separation (P2), truncation flag (P3), error envelope for model self-correction (P4), resource/source controls (P5) - Behavior verified against the shared `conformance/cases.jsonl` fixture ### `chdb/hypequery` DatabaseAdapter (#63, #65) Lets `@hypequery/clickhouse` query builder run on embedded chDB instead of a remote ClickHouse server. Uses hypequery's own `substituteParameters` (imported, not copied) so rendered SQL stays byte-identical to hypequery's built-in HTTP adapter. ### `chdb/connection` stabilization for @clickhouse/client 1.23.0 - `peerDependency: "@clickhouse/client: >=1.23.0"` (optional) — old client triggers a clear npm warning instead of a runtime TS error - Parity runner switched to track `ClickHouse/clickhouse-js` release tag `client-1.23.0` (per the sync-policy table; #52 shipped the initial hook) - Runner auto-detects upstream Vitest layout (pre-/post-clickhouse-js#931) - Runner re-synced when clickhouse-js reorganized into a monorepo (#62) ### AI-discovery docs (#60) - Top-level `AGENTS.md` + `llms-full.txt` - Per-subpath `AGENTS.md` (`src/layer3/`, `src/connection/`, etc.) - All packaged in `files` so agents crawling the installed tarball find them ## Fixes / infrastructure - Bind `null` / `undefined` query params as SQL `\N` (contributor @dfrankland, #68) — matches `@clickhouse/client` behavior - `close()`-mid-flight engine abort + ResultSet teardown race (#56) - `ChDBVector` HNSW granularity / filter binding / dimension guard (#61) - Release workflow: publish → `unverified` dist-tag → clean-install verify matrix → promote to `latest` (#64). A broken release now never reaches `latest` users. - CI: cache deps + harden npm registry fetch against transient resets ## User-facing installation matrix ``` npm install chdb # base + Layer 3 SDK npm install chdb @clickhouse/client # + chdb/connection npm install chdb @hypequery/clickhouse # + chdb/hypequery npm install chdb ai zod # + chdb/ai-sdk npm install chdb @mastra/core zod # + chdb/mastra ``` Native libchdb: chdb-core v26.5.1-rc.1 (packaged as @chdb/lib-* 26.5.2), unchanged from 3.1.0. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
What
First-class chDB integrations for the two main TypeScript agent frameworks, over one
shared, framework-agnostic executor core so behavior is identical across them.
Tools —
chdb/ai-sdkandchdb/mastra(chdbTools()returns three tools so anagent can discover then query, not guess SQL):
chdbListTables— tables/views in the session.chdbDescribeSource— columns/types of a table or any table function(
s3()/file()/postgresql()/…), via ClickHouseDESCRIBE TABLE.chdbQuery— read-only by default: reads run on a dedicated engine-levelread-only session (
SET readonly = 1) bound to the same data path, so a write/DDLis rejected by the engine, not just a prompt.
allowWriteopts out.Results are JSON (capped at
maxRows), engine errors are handed back to the modelrather than thrown.
chdbQueryTool()remains for the single-tool case.ChDBVector(chdb/mastra) — a Mastra vector store backed by chDB-core's HNSWvector_similarityindex, so similarity search is index-accelerated ANN (verified viaEXPLAIN indexes=1), not a brute-force scan. FullMastraVectorcontract; cosine +euclidean; idempotent upsert (ReplacingMergeTree + FINAL); flat metadata filters.
ChDBStore(chdb/mastra) — a Mastra storage adapter for the two columnar-friendlydomains: memory (threads/messages) and observability (AI tracing spans, so
trace/span data is analytically queryable with SQL). Records are stored as JSON +
indexed key columns in ReplacingMergeTree tables (upsert by id / (traceId,spanId), read
via FINAL). Other Mastra domains (workflows, scores, …) are point-update/OLTP-shaped and
left to the base — compose another backend for them.
Authored as ESM (
.mjs+.d.mts) because both frameworks are ESM-only; exposed via thechdb/ai-sdkandchdb/mastrasubpath exports.ai,@mastra/core, andzodareoptional peer dependencies.
Tests
23 integration tests, all green: the toolset (incl. read-only enforcement and engine
errors),
ChDBVector(ANN / filter / upsert-replace / describe / list / delete /generated ids), and
ChDBStore(thread+message round-trips and filters; span/traceread-write; updateSpan merge; listTraces status; an analytical
GROUP BYover spans).Full v3 suite green (390 passed).
🤖 Generated with Claude Code
Note
Add Vercel AI SDK and Mastra tool adapters under
chdb/ai-sdkandchdb/mastrasubpathsintegrations/chdb-tool-core.mjswith a framework-agnosticcreateChdbExecutorfactory that providesquery,listTables, anddescribeSourcemethods with read-only enforcement and capped row results (errors returned as strings, not thrown).integrations/ai-sdk.mjsexposing achdbToolsfactory andchdbQueryToolhelper for the Vercel AI SDK usingai.toolwith Zod input schemas.integrations/mastra.mjswith equivalent Mastra-compatible tools viacreateTool, plus re-exports ofChDBVector(HNSW vector store) andChDBStore(thread/message/span persistence).integrations/chdb-vector.mjsimplements aMastraVector-compatible store backed by chDB's HNSW index with upsert, ANN query, metadata filtering, and full index lifecycle management.integrations/chdb-store.mjsimplements Mastra memory (threads/messages) and observability (AI spans) storage usingReplacingMergeTreetables with paginated, filterable reads.chdb/ai-sdkandchdb/mastraare published viapackage.json;@mastra/core,ai, andzodare added as optional peer dependencies.Macroscope summarized eb7d0a9.