Variables
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!
Login
Before running any code, ensure you are logged in to the Afnio backend. See Logging in to Afnio Backend for other authentication options.
afnio login --relogin
Output:
Please enter your Tellurio API key and hit enter, or press ctrl+c to quit:
[afnio] API key provided and stored securely in local keyring.
[afnio] Currently logged in as 'username' to 'https://platform.tellurio.ai'. Use `afnio login --relogin` to force relogin.
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=Truewill 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
.dataattribute always gives you the underlying Python value (string, number, or list). - Assigning a
roleto a Variable helps clarify its purpose in your workflow and can improve interpretability during optimization.