Semantic operators
← Relifold documentation

SemJoin

Recover many-to-many semantic matches between an X Space and one or more Y Spaces.

The first input Space supplies X values. Remaining input Spaces form the Y side. For each X, SemJoin emits the Y values that satisfy the prompt's relationship.

SemJoin(
    name="match-needs-to-products",
    input_spaces=("customer-needs", "products"),
    output_space="recommended-products",
    primary_key="name",
    prompt=(
        "Match a customer need to a product only when the product directly "
        "addresses the stated need. Do not infer a match from broad category alone."
    ),
    provider="openai",
    model="gpt-5",
    reasoning_effort="low",
    x_batch_size=8,
    y_batch_size=20,
    rho=0.20,
    max_matches_per_x=5,
    max_candidate_pairs=10_000,
    match_only_if=lambda need, product:
        need["market"] == product["market"],
)

Output shape

Each output row retains the X fields and adds matches. Every entry in matches is the complete matched Y row—not a wrapper containing a nested value. A deterministic Transform can project the chosen catalog fields:

Transform(
    name="project-selected-product",
    input_spaces=("recommended-products",),
    output_space="selected-products",
    primary_key="name",
    fn=lambda row: {
        "product_id": row["matches"][0]["product_id"],
        "product_name": row["matches"][0]["name"],
    },
)
ParameterDefaultMeaning
input_spacesrequiredThe Space or Spaces read by the task.
output_spacerequiredThe Space that receives the task output.
namegeneratedA stable task name used in reviews, sessions, and metrics.
primary_keyidThe semantically meaningful field that identifies a value for this task. Rows with the same primary key represent one task value and produce one task result. Use Union first when every source identity must remain attached.
evidencenoneOptional supporting fields shown to the semantic model. Do not repeat a primary-key field or include irrelevant metadata.
promptrequiredThe criterion or transformation written in plain language.
provider / modelrequiredThe model used for this task's data decisions.
reasoning_effortlowOpenAI GPT-5-family reasoning effort. Use none for the lowest-latency, lowest-reasoning-cost path when the task contract is precise; increase it only after representative quality evidence justifies the added time and spend.
include_reasontrueWhether every model decision includes item-local visible evidence. The compiled prompt defaults to at most ten words; an explicit prompt request for longer visible reasoning is preserved. This is generation guidance; returned reasons are never truncated. Set false only for a validated task where result and confidence suffice.
confidence_thresholdoperator defaultExact boundary between unresolved and accepted evidence. Relifold compiles this value and operator-specific confidence guidance into the effective judge prompt.
function_parametersregistered gate defaultsTyped values for a generated semantic pair predicate such as merge_only_if, ancestor_only_if, match_only_if, or relate_only_if. The predicate declares the schema and finite candidates; reviewed evidence can then support parameter advice. Closed library gates do not take these bindings.
max_llm_callsunlimitedA hard task-level call ceiling. The task stops instead of silently exceeding it.
max_cost_usdunlimitedA hard task-level model-cost ceiling. A session-level ceiling may be stricter.
max_output_tokens2048Maximum generated tokens per model request. Increase it when one legitimate batch cannot fit its structured result.
ParameterDefaultMeaning
x_batch_sizeoperator defaultNumber of X values in one request.
y_batch_sizeoperator defaultNumber of Y candidates in one request.
rho0.0Additional quality-check allowance relative to initial semantic calls. A value of 0.20 allows up to 20% additional checks.
max_matches_per_xunlimitedMaximum Y matches emitted for each X.
max_matches_per_yunlimitedMaximum X matches emitted for each Y.
exact_xoperator defaultFill each X to its declared match bound using evaluated candidates only.
exact_yoperator defaultFill each Y to its declared match bound using evaluated candidates only.
max_candidate_pairsunlimitedFail before semantic calls when unresolved eligible X/Y pairs exceed this work ceiling.
match_only_ifnoneA deterministic eligibility gate applied before semantic comparison.

Primary keys across both sides

The declared primary-key columns must exist on both X and Y because the same key shape identifies values independently on each side. When the two Spaces use different names for their central fields, first use deterministic transforms to copy them to a common alias, such as semantic_value. Keep role-specific descriptions as evidence; do not build a composite key from columns that exist on only one side.

Cost shape

A join considers an X-by-Y candidate rectangle. Reduce it before semantic work: filter ineligible rows, use category or time gates, and keep only fields needed for the match. Bounds such as max_matches_per_x describe the result you want; they are not substitutes for a good eligibility predicate. max_candidate_pairs is a final pre-run guard over the unresolved eligible hand after compatible prior evidence is restored; it fails rather than silently returning a partial relationship result.

A rejected match_only_if pair is pair-local. It does not imply that another X or Y cannot match the same endpoint. The separate cardinality bounds can retire remaining candidates only after an endpoint reaches its declared match capacity.

Maximum versus exact cardinality

max_matches_per_x=3 with exact_x=false emits zero through three supported matches. With exact_x=true, every X must emit exactly three matches; the operator fills any deficit from the least-negative evaluated candidates. Use exactness only when the business output requires that count for every X. If no match is valid, or the instruction says “at most” or “only supported matches,” leave exactness false. Failed, exhausted, and budget-skipped pairs are never converted into matches. If evaluation does not cover enough candidates, the declared exact count remains unmet and is visible in the task output and failed-call metrics; treat that as a failed business invariant rather than accepting a partial result.

SemJoin versus SemMatching

Use SemJoin when values may have many matches. Use SemMatching when the output itself must be one-to-one, optionally perfect.