Variables#

Warning

Before running any code, ensure you are logged in to the Afnio backend (afnio login).. See Logging in to Afnio Backend for details.

Afnio Variables are the fundamental data structure for representing inputs, outputs, intermediate values and parameters in an agent or workflow. They can hold strings, numbers, or lists of these, and support automatic differentiation for optimization and learning.

Variables are similar to tensors in deep learning frameworks, but are designed for flexible, language-centric AI workflows. If you’re familiar with PyTorch tensors or NumPy arrays, you’ll find Afnio Variables intuitive. If not, follow along!


Initializing a Variable#

Afnio Variables are always initialized from Python data types: a string, a number, or a list or tuple containing these.

Examples

from afnio import Variable

# Scalar string
text = Variable("Hello, world!")

# Scalar number
score = Variable(42)

# List of strings (e.g., inputs, or messages)
inputs = Variable(["I love this!", "Needs improvement.", "All good."])

# List of numbers (e.g., scores)
scores = Variable([1, 2, 3, 4])

Role and Gradient Tracking

You can assign a semantic role to a Variable to describe its purpose in your workflow (e.g., “system prompt”, “user query”, “feedback”). Setting requires_grad=True means the Variable will collect gradients (semantic feedback) during backpropagation, which are then used to improve the Variable—whether it represents the whole prompt or just a part of it—during optimization.

Note

In Afnio, a “gradient” is semantic feedback—such as a suggestion to improve a prompt, a correction, or insights about edge cases in your training set—that is backpropagated through your workflow. Unlike deep learning, where gradients are numeric values, Afnio gradients are meaningful language or structured data that guide agent improvement.

# Example: system prompt for a sentiment classifier
system_prompt = Variable(
    "You are a helpful assistant. Classify the sentiment of this message.",
    role="system prompt for sentiment classification",
    requires_grad=True
)

Here, if the Variable represents a prompt (or part of a prompt), its role helps the Automatic Differentiation engine interpret its function, and requires_grad=True enables the prompt (or part of a prompt) to be refined automatically during optimization.


Attributes of a Variable#

Variables have several useful attributes:

x = Variable(
    ["fruit", "vegetable", "meat"],
    role="type of food",
    requires_grad=True
)

print(x.data)           # The raw data (string, number, list, or tuple)
print(x.role)           # Semantic role (e.g., "scores", "input sentiment", "classification output")
print(x.requires_grad)  # Whether this variable will collect gradients (feedback) during backpropagation
print(x.grad)           # The gradient (populated after backward; semantic feedback for optimization)
print(x.is_leaf)        # True if this variable was created directly by the user (not by an operation)
print(x.output_nr)      # Output number in the computation graph (advanced usage)
print(x.grad_fn)        # The function that created this variable, if any (None for leaf variables)

Output:

['fruit', 'vegetable', 'meat']
type of food
True
[]
True
0
None

Operations on Variables#

Afnio Variables support arithmetic, string, and list operations, as well as automatic differentiation. Operations are organized into macro categories:

  • Basic operations: arithmetic, string, and list manipulations for merging, and splitting Variables. These are useful for composing prompts, handling inputs and outputs, and building computation graphs with gradient tracking.

  • Language Model (LM) operations: interact with language models for chat completions. These APIs make it easy to integrate LM calls while preserving the multi-turn chat completion experience and low-level control offered by providers like OpenAI and Anthropic.

  • Evaluation operations: deterministic metrics and LM-as-Judge modules for scoring, feedback, and semantic evaluation. These are useful for measuring agent performance and collecting feedback.

See the Functional API and Modular API for a full list of available operations.

If any of the input Variables to an operation has requires_grad=True, the result will also have requires_grad=True and will track gradients during optimization. This ensures that all Variables involved in a computation graph can participate in automatic differentiation and receive feedback for learning.

Addition and concatenation

# Scalar string
a = Variable("abc", requires_grad=True)
b = Variable("def")
c = a + b
print("requires_grad:", c.requires_grad, "data:", c.data)

# List of strings
d = Variable(["foo", "bar"], requires_grad=True)
e = Variable(["baz", "qux"])
f = d + e
print("requires_grad:", f.requires_grad, "data:", f.data)

# Scalar float, no gradient tracking
g = Variable(1.5)
h = Variable(2.5)
i = g + h
print("requires_grad:", i.requires_grad, "data:", i.data)

# List of floats
j = Variable([1.1, 2.2, 3.3], requires_grad=True)
k = Variable([4.4, 5.5, 6.6])
l = j + k
print("requires_grad:", l.requires_grad, "data:", l.data)

Output:

requires_grad: True data: abcdef
requires_grad: True data: ['foobaz', 'barqux']
requires_grad: False data: 4.0
requires_grad: True data: [5.5, 7.7, 9.899999999999999]

In-place operations

In-place operations modify the variable directly, rather than creating a new one. This includes operations like x += y and x.copy_(y).

# Scalar string
m = Variable("foo", requires_grad=True)
n = Variable("bar")
m += n
print("requires_grad:", m.requires_grad, "data:", m.data)

# List of strings, no gradient tracking
o = Variable(["foo", "bar"])
p = Variable(["baz", "qux"])
o += p
print("requires_grad:", o.requires_grad, "data:", o.data)

# Scalar number
q = Variable(10, requires_grad=True)
r = Variable(5)
q += r
print("requires_grad:", q.requires_grad, "data:", q.data)

# List of numbers
s = Variable([1, 2, 3], requires_grad=True)
t = Variable([4, 5, 6])
s += t
print("requires_grad:", t.requires_grad, "data:", t.data)

Output:

requires_grad: True data: foobar
requires_grad: False data: ['foobaz', 'barqux']
requires_grad: True data: 15
requires_grad: False data: [4, 5, 6]

Note

In Afnio, in-place operations are denoted by a trailing underscore (_) in the method name (e.g., copy_, requires_grad_). This signals that the operation will modify the variable directly. Use them with care, as they may affect gradient tracking and computation history.

# In-place copy
x = Variable([1, 2, 3])
y = Variable([4, 5, 6])
x.copy_(y)
print(x.data)
print("requires_grad before:", x.requires_grad)

# Set requires_grad to True
x.requires_grad_()
print("requires_grad after:", x.requires_grad)

Output:

[4, 5, 6]
requires_grad before: False
requires_grad after: True

Automatic Differentiation#

Variables can track operations for gradient-based optimization.

# Compose a message
x = Variable(
    "The weather is nice today.",
    role="weather update statement",
    requires_grad=True
)
y = Variable(
    "Let's go for a walk.",
    role="activity suggestion"
)
z = x + y

# Simulate feedback requesting improvement (e.g., from a human reviewer or evaluation module)
feedback = Variable(
    "Try to make the message more engaging and add a question for the reader.",
    role="reviewer feedback for message improvement"
)

# Backpropagate feedback
z.backward(feedback)
print(x.grad)  # Populated after backward with improvement suggestions

Output:

[Variable(data=Here is the combined feedback we got for this specific weather update statement and other variables: Try to make the message more engaging and add a question for the reader., role=feedback to weather update statement, requires_grad=False)]

Type Conversion#

You can cast the data of a Variable to another type:

x = Variable("123")
y = x.to(int)
print(x.data, type(x.data))
print(y.data, type(y.data))

Output:

123 <class 'str'>
123 <class 'int'>

Bridge with Python Numbers Lists and Strings#

Variables can be initialized from and converted to Python numbers, lists, strings, or numbers.

x = Variable([1, 2, 3])
lst = x.data
print(lst, type(lst))

y = Variable("hello")
s = y.data
print(s, type(s))

z = Variable(42)
num = z.data
print(num, type(num))
[1, 2, 3] <class 'list'>
hello <class 'str'>
42 <class 'int'>

Notes#

  • Variables initialized with requires_grad=True will collect feedback and participate in automatic differentiation.

  • In-place operations (e.g., x += y, x.copy_(y), x.requires_grad_()) modify the variable directly and may affect gradient tracking.

  • The .data attribute always gives you the underlying Python value (string, number, or list).

  • Assigning a role to a Variable helps clarify its purpose in your workflow and can improve interpretability during optimization.