Skip to content

Data Model Operators

Data Model Operators

In previous lessons, you learned to create branches where some outputs can be None. How do we combine these outputs? This lesson introduces operators for merging and manipulating data models.

The Five Operators

graph LR
    subgraph Operators
        CONCAT[A + B: Concatenate]
        AND[A & B: Safe Merge]
        OR[A | B: First Non-None]
        XOR[A ^ B: Exclusive]
        NOT[~A: Cancel]
    end

Synalinks provides five Python operators for data models:

Operator Name Behavior
+ Concatenation Merge fields (fails if either is None)
& Logical AND Safe merge (returns None if either is None)
| Logical OR Returns first non-None, or merges if both exist
^ Logical XOR Returns data only if exactly ONE is non-None
~ NOT (Invert) Converts data to None

1. Concatenation (+)

Combines fields from two data models into one:

answer = Answer(answer="42")           # {"answer": "42"}
thinking = Thinking(thinking="...")    # {"thinking": "..."}

combined = answer + thinking  # {"answer": "42", "thinking": "..."}

Warning: Raises an exception if either operand is None!

2. Logical AND (&)

Safe concatenation - returns None if either input is None:

result = data & possibly_none  # None if possibly_none is None

3. Logical OR (|)

Returns non-None value, or merges if both have data:

(easy_result, hard_result) = await Branch(...)(inputs)
final = easy_result | hard_result  # Gets whichever has data

4. Logical XOR (^)

Returns data only if exactly one operand is non-None:

result = a ^ b
# If a has data and b is None: returns a
# If a is None and b has data: returns b
# If both have data: returns None
# If both are None: returns None

5. NOT / Invert (~)

Converts any data to None:

result = ~data  # Always returns None

Useful for conditional flows where you want to "cancel" a path.

Truth Table

A B A + B A & B A | B A ^ B
Data Data Merged Merged Merged None
Data None ERROR None A A
None Data ERROR None B B
None None ERROR None None None
A ~A
Data None
None None*

*Note: ~None behavior depends on context (symbolic vs json)

Complete Example: Merging Branch Outputs

import asyncio
from dotenv import load_dotenv
import synalinks

class Query(synalinks.DataModel):
    query: str = synalinks.Field(description="The user query")

class Answer(synalinks.DataModel):
    answer: str = synalinks.Field(description="The correct answer")

async def main():
    load_dotenv()
    language_model = synalinks.LanguageModel(model="openai/gpt-4.1")

    inputs = synalinks.Input(data_model=Query)

    # Branch returns (easy_result, hard_result) - only ONE is non-None
    (easy, hard) = await synalinks.Branch(
        question="Evaluate difficulty",
        labels=["easy", "difficult"],
        language_model=language_model,
        branches=[
            synalinks.Generator(data_model=Answer, language_model=language_model),
            synalinks.Generator(data_model=Answer, language_model=language_model),
        ],
    )(inputs)

    # Use | to get whichever branch was active
    outputs = easy | hard

    program = synalinks.Program(inputs=inputs, outputs=outputs)

    result = await program(Query(query="What is 2 + 2?"))
    print(f"Answer: {result['answer']}")  # Gets the non-None result

asyncio.run(main())

Key Takeaways

  • Concat (+): Merge all fields from two data models into one. Both inputs must have data (no None).
  • OR (|): Returns the first non-None input. Perfect for merging conditional branch outputs.
  • AND (&): Returns data only if both inputs have data; otherwise returns None.
  • XOR (^): Returns data only if exactly one input has data; returns None if both or neither have data.
  • NOT (~): Always returns None. Useful for canceling paths in conditional flows.

Program Visualizations

concatenation logical_or logical_and

API References

Answer

Bases: DataModel

A simple answer.

Source code in examples/5a_data_model_operators.py
class Answer(synalinks.DataModel):
    """A simple answer."""

    answer: str = synalinks.Field(description="The correct answer")

AnswerWithThinking

Bases: DataModel

An answer with reasoning.

Source code in examples/5a_data_model_operators.py
class AnswerWithThinking(synalinks.DataModel):
    """An answer with reasoning."""

    thinking: str = synalinks.Field(description="Your step by step thinking")
    answer: str = synalinks.Field(description="The correct answer")

Critique

Bases: DataModel

A critique of an answer.

Source code in examples/5a_data_model_operators.py
class Critique(synalinks.DataModel):
    """A critique of an answer."""

    critique: str = synalinks.Field(description="The critique of the answer")

Query

Bases: DataModel

A user query.

Source code in examples/5a_data_model_operators.py
class Query(synalinks.DataModel):
    """A user query."""

    query: str = synalinks.Field(description="The user query")