Skip to content

v0.2.2

Latest

Choose a tag to compare

@kwangaws kwangaws released this 27 Aug 21:44
42b5c6b

Release Notes — v0.2.2

Highlights

This release makes the accelerator deployable outside the US without editing source, makes non-Latin questions actually retrievable, and adds an opt-in agentic Tier-2 strategy for hard analytic SQL.

Every Bedrock model ID is now a deploy-config key, and the resolved values drive the container environment, the IAM grants, and the cost dashboards from one place — so a non-US deployment no longer has to edit six files and still end up with dark widgets. Tier-2/Tier-3 retrieval gains bidirectional label containment, which is what lets a Chinese, Japanese, Thai, Korean or Indic-script question reach a stored label the serve layer cannot segment. Tier 2 also gains a caller-pinnable options.strategy, including a bounded tool-use agent that discovers schemas, generates, executes and self-corrects.

It closes 3 field-filed bugs (#92, #94, #95), removes the last AGPL-licensed dependency from the document pipeline, fixes a family of silent failures (a truncated LLM generation reported as HTTP 200, a row cap that landed inside a SQL comment, matched classes dropped past a row limit, a masked answer counted as allowed), and adds ~6,300 lines of unit tests.


What's New

Deploy outside us-east-1 with no source edits

  • Every Bedrock model ID is now an SSM deploy-config key (/{prefix}/config). Each falls back to its existing default, so a deployment that sets none of them behaves exactly as before.

    Config key Sets the model for
    bedrockLlmModelId Serve query LLM (NL→SPARQL, synthesis)
    bedrockEmbedModelId All embeddings — induction, doc-KG-build, metric matching, serve retrieval
    bedrockEmbedDimensions Vector dimension for the embedding model above
    bedrockInductionLlmModelId Induction, grounding rerank, description generation
    bedrockChatModelId Source enrichment, constraint inference, doc-ingestion extraction
  • Both geographic inference profiles (us./eu./apac./jp./global.) and bare in-region model IDs are accepted — bare IDs matter because some models publish geo profiles for only a subset of regions. A new bedrockModelArn() helper picks the right ARN shape per form (inference-profile/… vs foundation-model/…), so IAM grants can no longer fail closed on the wrong shape.

  • Model IDs are resolved once per stack and reused for the container environment, the Bedrock IAM grants, and the CloudWatch dashboards' ModelId dimensions — previously duplicate literals, so changing a model left cost widgets querying a model nothing invokes.

  • Preflight got two new checks: CDK bootstrap in both the deploy region and us-east-1 (the *-edge-waf stack always deploys there), and a warning when a configured model ID isn't listed in the deploy region.

Multilingual query understanding

  • Bidirectional label containment in Tier-2 T-Box fallback and Tier-3 keyword entity search. The forward direction (stored_label CONTAINS term) is unchanged; the new reverse direction (query_run CONTAINS stored_label) finds a stored label sitting inside a query word — which is what a scriptless-boundary language requires, since the serve layer cannot segment Chinese/Japanese/Thai/Khmer or strip the particles Korean, Hindi and Bengali agglutinate onto a noun. No per-language segmenter, morphology table, or stop-word list is needed: the graph already stores the exact words as labels.
  • Tokenization moved to Unicode extended grapheme clusters (UAX #29, via regex). Python re's \w excludes combining marks, which split Thai/Indic words and split İstanbul after lowercasing.
  • Latin and Greek are deliberately excluded from reverse containment (reliable whitespace boundaries; Greek inflection rewrites the ending rather than appending). Cyrillic is included because case forms append to the nominative stem.

Opt-in agentic Tier-2 strategy

  • options.strategy lets a caller pin how Tier 2 produces its query: best, ontop, nl_to_sql, ontop_first, nl_to_sql_first (default, unchanged), and the new agentic.
  • agentic is a bounded Strands tool-use agent that starts from pre-fetched candidate tables and schemas, discovers more as needed (search_tables / get_table_schema), delegates generation (generate_sql), then executes and self-corrects (execute_sql). It has no raw-SQL execution path: generate_sql returns a handle, and only a generated statement can be executed. It is never part of an automatic or fallback chain — it runs solely when pinned. The loop is bounded by the request deadline and SERVE_AGENTIC_EXEC_TIMEOUT_S, not by a turn count.
  • New reusable Tier-2 tooling (tier2/tools/{sql_tools,table_tools,graph_tools}.py) and a shared execute-with-authz primitive (sql_execution.py) sit under both the agentic and flat paths.
  • Tuning knobs: SERVE_AGENTIC_PREFETCH_SCHEMAS, SERVE_AGENTIC_EXEC_TIMEOUT_S, SERVE_AGENTIC_GRAPH_TRAVERSAL, SERVE_AGENTIC_INTENT_REVIEW.

Experimental: ontology FK-graph expansion for flat NL→SQL

  • SERVE_NL2SQL_GRAPH_EXPAND (deployment-wide) / options.flatGraphExpand (per request) appends the tables one induced foreign-key hop from the retrieved classes — with their columns — to the flat NL→SQL schema context. The flat path retrieves once and never re-retrieves, so a table similarity didn't rank has no other route into the prompt.
  • Measured +6 to +12.5pp retrieval full-hit across three cells (pooled Spider 2.0, per-DB Spider 2.0, BIRD) and +2.2 to +5.2pp execution accuracy in all four Spider 2.0 arm×seed comparisons (44 win / 25 loss, p=0.030) at unchanged latency. Ships off: no single cell reaches significance on its own and BIRD is a two-repeat wash. Capped by SERVE_NL2SQL_GRAPH_EXPAND_MAX_TABLES (default 15).

Guardrail outcomes are now separable

  • GuardrailInvocations gains a Decision dimension covering ALLOW / ANONYMIZED / BLOCK / UNKNOWN / MODEL_FILTERED, plus a new GuardrailUnknown metric.
  • Masking (PII NAME{NAME}) is no longer invisible — an NTSB/TimeBank benchmark found 68% of one arm's answers silently carrying placeholders that no existing metric would have surfaced.

Ontology induction reports what it dropped

  • InductionReport gains droppedTables (tableName + reason, e.g. gen_llm_empty / parse_failed), so a table the generation LLM silently failed on is now reported instead of quietly missing from the proposal. Empty for strategies that never drop tables.

Ontology review UI: ambiguous grounding is visible

  • skos:relatedMatch now renders as an "Ambiguous" origin badge ("low confidence, review before accepting") in the proposal table, class detail panel, and grounding tab, sorting between Grounded and Novel.

Bounded query length at every boundary

  • A new NaturalLanguageQuery Smithy shape and shared validate_query_text() bound NL queries at 4,000 code points (not bytes, so the allowance doesn't shrink for non-Latin scripts) at the Data Layer, the Serve entry points, and the request model. The API's WAF separately caps the request body at 8,192 bytes; that ceiling is now documented with measurements so a generated client doesn't accept 4,000 characters and then surface an unexplained 403.

Documentation

  • Oracle and Snowflake are documented as federation-only — no direct-SQL driver on the serve path, so every query runs through the Athena federated catalog and queryEngine is always ATHENA.
  • Snowflake onboarding is fully specified: warehouse is mandatory (federation is rejected at connection-creation time without it), the optional role is a Snowflake RBAC role honored during discovery only, port 443 + port 80 (OCSP) egress and the connector_ocsp_egress / connector_egress_cidrs context keys, plus two new troubleshooting rows.
  • The "Where the model IDs live" section is rewritten around the new config keys, with an ap-northeast-1 example and a warning that nothing validates model IDs at synth time.

Groundwork: custom Athena federation connectors

  • API contract for a new ATHENA_CONNECTOR database sub-type (AthenaConfiguration: metadataFunctionArn, optional recordFunctionArn, databaseName, table filters), a validated LambdaFunctionArn shape that rejects Lambda's partial-ARN and name-only forms, the Athena data-catalog lifecycle IAM grant, and a parser for @pk / @fk(...) constraint tags carried in column comments (the federation protocol has no field for key constraints). Not yet onboardable — the registrar handler lands in a following release.

Bug Fixes

Deployment & Regions

  • Fixed every MCP tool failing outside us-east-1. MCPConfig.aws_region defaulted to a hardcoded us-east-1 unless SCL_AWS_REGION was set — and nothing in infra/ sets it (the MCP stack sets AWS_REGION). Every downstream call (Lambda invokes, Context Manager endpoint) was pinned to us-east-1. Region now resolves through a shared resolve_region() (AWS_REGIONAWS_DEFAULT_REGIONus-east-1) via default_factory, keeping pydantic's env precedence intact. The same fix applies to the shared SCLConfig. (#92)
  • Fixed deploying to a non-US region requiring source edits. Model IDs were hardcoded literals scattered across CDK stacks, Python runtime fallbacks and TypeScript constants, so a non-US deploy meant editing source in several places — and missing one left that path invoking a us. profile that Bedrock rejects at first invocation with ValidationException. Three previously unset paths are now configured explicitly: table enrichment (BEDROCK_CHAT_MODEL_ID, which had no model env var and always took the us. default), doc-KG-build embedding, and the doc-ingestion trigger's BEDROCK_MODEL_ARN. (#94, supersedes #93)
  • Fixed the deploy failing partway through on the *-edge-waf stack for non-us-east-1 deployments. That stack always deploys to us-east-1, so two bootstraps are required; the docs showed one and preflight didn't check. Preflight now verifies both regions and fails fast, and deploying.md documents it.

Query & Retrieval (Serve)

  • Fixed the Tier-1 residual-qualifier gate being inert for Korean. The gate's tokenizer was ASCII-only ([a-z0-9_%$]+), so a Korean question produced zero residual tokens and sailed through as if it had no unconsumed qualifiers — the exact confident-but-wrong metric answer the gate was added in v0.2.1 to prevent. Hangul syllables are now tokenized, and Korean question scaffolding (request verbs, question words, aggregate-restating counters, connectives) is added to the stop-word groups. (#95 — Korean; Han/Kana/Thai tokenization remains open)
  • Fixed NL→SQL silently truncating long analytic queries. Generation was capped at 1,024 output tokens. Bedrock returns a cap-hit generation as a partial body with HTTP 200, and the extractor accepted it — so the symptom was a confusing execution-time syntax error, not a generation error. Measured against 867 generated Spider 2.0 queries, ~5% exceed 1,024 tokens of SQL alone, and on a reasoning model (which spends the same budget on thinking blocks first) roughly half exceeded what was left. The cap is now 4,096, configurable via SERVE_NL2SQL_MAX_TOKENS (clamped to 512–32768, clamping logged), and truncation is surfaced explicitly (ConverseResult.stop_reason / truncated, plus an nl_to_sql_generation_truncated warning).
  • Fixed the Athena row cap being silently removed by a trailing SQL comment. When the LIMIT couldn't be injected via the parse tree, it was appended on the same line — and LLM-generated SQL frequently ends with a -- comment line, so the cap landed inside the comment. The query still executed, just unbounded. The fallback now appends LIMIT on its own line.
  • Fixed matched classes silently vanishing from the Tier-2 prompt. The keyword-fallback T-Box query joined classes against a datatype-property OPTIONAL, so one class fanned out to (properties × distinct values × parents × labels) rows; with no ORDER BY, a class whose rows all landed past the 2,000-row cap disappeared entirely. Reverse containment made this reachable (it matches far more classes, and a multilingual namespace carries one label per language). Now split into a DISTINCT class query plus a bounded property query, de-duplicated before the VALUES cap, with truncation logged rather than silent.
  • Fixed the SQL firewall rejecting already-transpiled statements as unparseable. evaluate() always parsed as trino; it now takes a dialect so the direct-JDBC path can pass the engine's own dialect.

Guardrails & Observability

  • Fixed a masked answer being counted as allowed. stopReason=guardrail_intervened is used for masking and blocking alike, so the two were indistinguishable. Outcomes are now classified (GuardrailOutcome: NONE / ANONYMIZED / BLOCKED / UNKNOWN) and a merely-anonymized answer is no longer discarded as blocked content.
  • Fixed an unconfirmed suppression being reported as a confirmed policy block. A guardrail that intervened without returning a trace can't be classified; the call is still suppressed (fail closed) but now increments GuardrailUnknown instead of GuardrailBlocked, so the two numbers stay recoverable. A non-zero GuardrailUnknown means traces stopped arriving — previously that destroyed answers silently.
  • Fixed a model's own output filter being read as a guardrail block. stopReason=content_filtered is native model-side filtering, not a Bedrock Guardrail policy action. It now raises a typed ContentFilteredError and reports MODEL_FILTERED.
  • Guardrail trace logging now emits redacted assessment actions — traces carry the raw matched text.

Documents (Ingestion)

  • Removed the last AGPL-licensed dependency. PDF scanned-detection and 300-DPI page rendering moved from PyMuPDF (AGPL-3.0) to pypdfium2 + pillow (permissive). owlready2 (LGPL-3.0) is now the only entry in the external-dependency license table.
  • Fixed a malformed PDF raising a raw PDFium traceback. Open/extract/render failures are now caught and logged with a reason: scanned-detection reports "scanned" (a usable verdict), page count reports 0, and Textract rendering returns empty rather than crashing the task.
  • Added resource guards on PDF rasterization. A few thousand pages, or one page with absurd dimensions, compresses to almost nothing on disk but expands to gigabytes of bitmap at 300 DPI — enough to OOM the task. Now capped at 1,000 pages and 50 MP per rendered page, both checked before any bitmap is allocated, with a clear per-file error. Native bitmap and textpage handles are closed explicitly rather than left to GC.

Sources (API)

  • Fixed GET /sources/{id} returning 500 for a database sub-type with no config mapping. The sources table stores the config blob in one untyped column, and anything that wasn't JDBC_DATABASE was reported as glueConfiguration — which requires catalogId and region, so response validation failed and a cosmetic mislabel became a 500. Sub-type → config member is now an explicit map, with an unmapped sub-type logged rather than silently read as Glue.

Ontology Review UI

  • Fixed a class with only a sibling-proposal skos match rendering as "Grounded" with a blank target. A body.includes(...) fallback set matchType even when no foundational target resolved, so matchType and groundedTo could disagree. matchType is now derived solely from the resolved foundational match, so a sibling-only (dedup) match correctly renders "Novel".

Dependency & Base Image Updates

  • All six service images rebased onto a newer pinned python:3.12-slim digest (context-manager, ontology-engine, vkg, sources DB-enrichment, doc kg-build, doc preprocessing).
  • pypdfium2>=4.30.0 + pillow>=12.3.0 replace PyMuPDF>=1.28.2 in document preprocessing.
  • regex==2026.5.9 added to context-manager for UAX #29 grapheme segmentation.

Upgrading from v0.2.1

make deploy-dev — no migration steps for an existing us-east-1 deployment. Defaults are unchanged, options.strategy defaults to today's nl_to_sql_first, and both graph-expansion flags ship off.

Two things to know before you set the new config keys:

  • bedrockEmbedModelId and bedrockEmbedDimensions are initial-deploy settings. All embeddings must come from the same model, and the dimension is baked into the OpenSearch index at creation. Changing either on a deployment that already holds data is a re-ingestion, not a config change.
  • Nothing validates model IDs at synth time. A deploy with unusable IDs still reaches CREATE_COMPLETE and fails at first invocation. Check availability in your target region (aws bedrock list-inference-profiles, aws bedrock list-foundation-models) — preflight now warns, but only when the AWS CLI and jq are available.