跳转至

Query IR#

nlql.ir —— Query IR:规范、可 JSON 序列化的查询 AST(Select / Binding / Expr 家族),三种入口的公共归宿。query_json_schema() 导出 JSON Schema,可直接作为 LLM function-calling 的参数 schema。

ir #

Query IR: the canonical, JSON-serializable form of every NLQL query.

Expr module-attribute #

Expr = Union['Literal', 'Path', 'Ref', 'Call', 'Compare', 'And', 'Or', 'Not']

__all__ module-attribute #

__all__ = ['Query', 'Select', 'Binding', 'OrderKey', 'Expr', 'Literal', 'Path', 'Ref', 'Call', 'Compare', 'And', 'Or', 'Not', 'expr_from_dict', 'query_json_schema']

And dataclass #

And(operands: list[Expr])

Boolean AND over two or more operands.

operands instance-attribute #

operands: list[Expr]

KIND class-attribute #

KIND: str = 'and'

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"node": self.KIND, "operands": [o.to_dict() for o in self.operands]}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> And
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> And:
    return cls(operands=[expr_from_dict(o) for o in d["operands"]])

Binding dataclass #

Binding(name: str, expr: Expr)

A LET name = expr named binding.

name instance-attribute #

name: str

expr instance-attribute #

expr: Expr

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"name": self.name, "expr": self.expr.to_dict()}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Binding
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Binding:
    return cls(name=d["name"], expr=expr_from_dict(d["expr"]))

Call dataclass #

Call(name: str, args: list[Expr] = list())

A function or operator call, resolved against the registry by name.

name instance-attribute #

name: str

args class-attribute instance-attribute #

args: list[Expr] = field(default_factory=list)

KIND class-attribute #

KIND: str = 'call'

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"node": self.KIND, "name": self.name, "args": [a.to_dict() for a in self.args]}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Call
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Call:
    return cls(name=d["name"], args=[expr_from_dict(a) for a in d.get("args", [])])

Compare dataclass #

Compare(op: str, left: Expr, right: Expr)

An infix comparison; op in == != < > <= >=.

op instance-attribute #

op: str

left instance-attribute #

left: Expr

right instance-attribute #

right: Expr

KIND class-attribute #

KIND: str = 'compare'

__post_init__ #

__post_init__() -> None
源代码位于: src/nlql/ir/nodes.py
def __post_init__(self) -> None:
    if self.op not in _COMPARE_OPS:
        raise NLQLSchemaError(f"unknown comparison op {self.op!r}")

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {
        "node": self.KIND,
        "op": self.op,
        "left": self.left.to_dict(),
        "right": self.right.to_dict(),
    }

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Compare
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Compare:
    return cls(op=d["op"], left=expr_from_dict(d["left"]), right=expr_from_dict(d["right"]))

Literal dataclass #

Literal(value: LiteralValue, type_hint: str | None = None)

A constant scalar value, optionally carrying an explicit type hint (e.g. DATE '2024-01-01' → type_hint='date').

value instance-attribute #

value: LiteralValue

type_hint class-attribute instance-attribute #

type_hint: str | None = None

KIND class-attribute #

KIND: str = 'literal'

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    d: dict[str, Any] = {"node": self.KIND, "value": self.value}
    if self.type_hint is not None:
        d["type_hint"] = self.type_hint
    return d

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Literal
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Literal:
    if "value" not in d:
        raise NLQLSchemaError("literal node requires 'value'")
    return cls(value=d["value"], type_hint=d.get("type_hint"))

Not dataclass #

Not(operand: Expr)

Boolean negation.

operand instance-attribute #

operand: Expr

KIND class-attribute #

KIND: str = 'not'

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"node": self.KIND, "operand": self.operand.to_dict()}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Not
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Not:
    return cls(operand=expr_from_dict(d["operand"]))

Or dataclass #

Or(operands: list[Expr])

Boolean OR over two or more operands.

operands instance-attribute #

operands: list[Expr]

KIND class-attribute #

KIND: str = 'or'

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"node": self.KIND, "operands": [o.to_dict() for o in self.operands]}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Or
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Or:
    return cls(operands=[expr_from_dict(o) for o in d["operands"]])

OrderKey dataclass #

OrderKey(expr: Expr, desc: bool = False)

One ORDER BY key.

expr instance-attribute #

expr: Expr

desc class-attribute instance-attribute #

desc: bool = False

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"expr": self.expr.to_dict(), "desc": self.desc}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> OrderKey
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> OrderKey:
    return cls(expr=expr_from_dict(d["expr"]), desc=bool(d.get("desc", False)))

Path dataclass #

Path(root: str, segments: list[str] = list())

A field path: a root plus zero or more dotted segments.

Roots in v1: content (the unit text) and meta (business metadata), e.g. Path("meta", ["status"]) for meta.status.

root instance-attribute #

root: str

segments class-attribute instance-attribute #

segments: list[str] = field(default_factory=list)

KIND class-attribute #

KIND: str = 'path'

dotted property #

dotted: str

__post_init__ #

__post_init__() -> None
源代码位于: src/nlql/ir/nodes.py
def __post_init__(self) -> None:
    if not self.root:
        raise NLQLSchemaError("path node requires a non-empty root")

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"node": self.KIND, "root": self.root, "segments": list(self.segments)}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Path
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Path:
    return cls(root=d["root"], segments=list(d.get("segments", [])))

Query dataclass #

Query(select: Select, let: list[Binding] = list(), where: Expr | None = None, order_by: list[OrderKey] = list(), limit: int | None = None)

The canonical Query IR.

select instance-attribute #

select: Select

let class-attribute instance-attribute #

let: list[Binding] = field(default_factory=list)

where class-attribute instance-attribute #

where: Expr | None = None

order_by class-attribute instance-attribute #

order_by: list[OrderKey] = field(default_factory=list)

limit class-attribute instance-attribute #

limit: int | None = None

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    d: dict[str, Any] = {"select": self.select.to_dict()}
    if self.let:
        d["let"] = [b.to_dict() for b in self.let]
    if self.where is not None:
        d["where"] = self.where.to_dict()
    if self.order_by:
        d["order_by"] = [k.to_dict() for k in self.order_by]
    if self.limit is not None:
        d["limit"] = self.limit
    return d

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Query
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Query:
    if "select" not in d:
        raise NLQLSchemaError("query requires a 'select' clause")
    where = d.get("where")
    return cls(
        select=Select.from_dict(d["select"]),
        let=[Binding.from_dict(b) for b in d.get("let", [])],
        where=expr_from_dict(where) if where is not None else None,
        order_by=[OrderKey.from_dict(k) for k in d.get("order_by", [])],
        limit=d.get("limit"),
    )

to_json #

to_json(**kwargs: Any) -> str
源代码位于: src/nlql/ir/nodes.py
def to_json(self, **kwargs: Any) -> str:
    return json.dumps(self.to_dict(), **kwargs)

from_json classmethod #

from_json(s: str) -> Query
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_json(cls, s: str) -> Query:
    return cls.from_dict(json.loads(s))

Ref dataclass #

Ref(name: str)

A reference to a LET-bound alias.

name instance-attribute #

name: str

KIND class-attribute #

KIND: str = 'ref'

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    return {"node": self.KIND, "name": self.name}

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Ref
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Ref:
    return cls(name=d["name"])

Select dataclass #

Select(unit: str, window: int | None = None)

The SELECT clause: base granularity plus optional SPAN window.

window is the SPAN(<unit>, window => N) context radius; None means return the base unit itself.

unit instance-attribute #

unit: str

window class-attribute instance-attribute #

window: int | None = None

__post_init__ #

__post_init__() -> None
源代码位于: src/nlql/ir/nodes.py
def __post_init__(self) -> None:
    if self.unit not in _SELECT_UNITS:
        raise NLQLSchemaError(
            f"unknown SELECT unit {self.unit!r}; expected one of {sorted(_SELECT_UNITS)}"
        )
    if self.window is not None and self.window < 0:
        raise NLQLSchemaError("SPAN window must be >= 0")

to_dict #

to_dict() -> dict[str, Any]
源代码位于: src/nlql/ir/nodes.py
def to_dict(self) -> dict[str, Any]:
    d: dict[str, Any] = {"unit": self.unit}
    if self.window is not None:
        d["window"] = self.window
    return d

from_dict classmethod #

from_dict(d: dict[str, Any]) -> Select
源代码位于: src/nlql/ir/nodes.py
@classmethod
def from_dict(cls, d: dict[str, Any]) -> Select:
    return cls(unit=d["unit"], window=d.get("window"))

expr_from_dict #

expr_from_dict(d: Any) -> Expr

Rebuild an expression node from its dict form.

源代码位于: src/nlql/ir/nodes.py
def expr_from_dict(d: Any) -> Expr:
    """Rebuild an expression node from its dict form."""
    if not isinstance(d, dict) or "node" not in d:
        raise NLQLSchemaError(f"expression must be an object with a 'node' tag, got {d!r}")
    ctor = _EXPR_DISPATCH.get(d["node"])
    if ctor is None:
        raise NLQLSchemaError(f"unknown expression node {d['node']!r}")
    return ctor(d)

query_json_schema #

query_json_schema(function_names: Iterable[str] | None = None) -> dict[str, Any]

Return the JSON Schema (draft 2020-12) describing a Query IR document.

源代码位于: src/nlql/ir/schema.py
def query_json_schema(function_names: Iterable[str] | None = None) -> dict[str, Any]:
    """Return the JSON Schema (draft 2020-12) describing a Query IR document."""
    call_name: dict[str, Any] = {
        "type": "string",
        "description": "Function name. SIMILARITY(content, \"query\") = semantic relevance; CONTAINS(content, \"keyword\") = literal substring; LENGTH(content) = text length.",
    }
    if function_names is not None:
        call_name = {"type": "string", "enum": sorted(set(function_names))}

    expr_ref = {"$ref": "#/$defs/expr"}
    return {
        "$schema": _SCHEMA_URI,
        "title": "NLQLQuery",
        "description": "A semantic retrieval query. Use 'let' with SIMILARITY(content, \"query\") for relevance scoring, 'where' for filtering (metadata equality, CONTAINS for keyword matching, AND/OR for combining), 'order_by' for ranking, 'limit' to cap results.",
        "type": "object",
        "additionalProperties": False,
        "properties": {
            "select": {"$ref": "#/$defs/select"},
            "let": {
                "type": "array",
                "description": "Named scalar/score bindings referenced by WHERE and ORDER BY.",
                "items": {"$ref": "#/$defs/binding"},
            },
            "where": expr_ref,
            "order_by": {"type": "array", "items": {"$ref": "#/$defs/orderKey"}},
            "limit": {
                "type": ["integer", "null"],
                "minimum": 0,
                "description": "Max results. Pick by question breadth: 1-2 for specific facts, 5-10 for broad summaries. null/omit = return all matches ranked by relevance.",
            },
        },
        "required": ["select"],
        "$defs": {
            "select": {
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "unit": {"enum": ["document", "chunk", "sentence"], "description": "document = whole doc (meta/first page), chunk = passage (default), sentence = specific sentence"},
                    "window": {
                        "type": ["integer", "null"],
                        "minimum": 0,
                        "description": "SPAN context radius; omit for the base unit.",
                    },
                },
                "required": ["unit"],
            },
            "binding": {
                "description": "Named binding: give SIMILARITY a name (e.g. 'rel') to reference in WHERE and ORDER BY.",
                "type": "object",
                "additionalProperties": False,
                "properties": {"name": {"type": "string"}, "expr": expr_ref},
                "required": ["name", "expr"],
            },
            "orderKey": {
                "type": "object",
                "additionalProperties": False,
                "properties": {"expr": expr_ref, "desc": {"type": "boolean"}},
                "required": ["expr"],
            },
            "expr": {
                "oneOf": [
                    {"$ref": "#/$defs/literal"},
                    {"$ref": "#/$defs/path"},
                    {"$ref": "#/$defs/ref"},
                    {"$ref": "#/$defs/call"},
                    {"$ref": "#/$defs/compare"},
                    {"$ref": "#/$defs/and"},
                    {"$ref": "#/$defs/or"},
                    {"$ref": "#/$defs/not"},
                ]
            },
            "literal": {
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "node": {"const": "literal"},
                    "value": {"type": ["string", "number", "boolean", "null"]},
                    "type_hint": {
                        "type": "string",
                        "enum": ["date", "timestamp", "text", "number", "bool"],
                        "description": "Optional type hint for SQL-style typed literals (since v0.3.2).",
                    },
                },
                "required": ["node", "value"],
            },
            "path": {
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "node": {"const": "path"},
                    "root": {"type": "string", "description": "e.g. 'content' or 'meta'"},
                    "segments": {"type": "array", "items": {"type": "string"}},
                },
                "required": ["node", "root"],
            },
            "ref": {
                "type": "object",
                "additionalProperties": False,
                "properties": {"node": {"const": "ref"}, "name": {"type": "string"}},
                "required": ["node", "name"],
            },
            "call": {
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "node": {"const": "call"},
                    "name": call_name,
                    "args": {"type": "array", "items": expr_ref},
                },
                "required": ["node", "name"],
            },
            "compare": {
                "description": "Compare two values: meta.status == \"published\", meta.date >= \"2024-01-01\", ref(\"rel\") >= 0.5.",
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "node": {"const": "compare"},
                    "op": {"enum": ["==", "!=", "<", ">", "<=", ">="]},
                    "left": expr_ref,
                    "right": expr_ref,
                },
                "required": ["node", "op", "left", "right"],
            },
            "and": {
                "description": "All operands must match. Combine: relevance threshold + metadata filter + keyword.",
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "node": {"const": "and"},
                    "operands": {"type": "array", "items": expr_ref, "minItems": 2},
                },
                "required": ["node", "operands"],
            },
            "or": {
                "description": "Any operand can match. Use for alternatives: CONTAINS(content,\"x\") OR CONTAINS(content,\"y\").",
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "node": {"const": "or"},
                    "operands": {"type": "array", "items": expr_ref, "minItems": 2},
                },
                "required": ["node", "operands"],
            },
            "not": {
                "type": "object",
                "additionalProperties": False,
                "properties": {"node": {"const": "not"}, "operand": expr_ref},
                "required": ["node", "operand"],
            },
        },
    }