# Variables ```{warning} Before running any code, ensure you are logged in to the Afnio backend (`afnio login`).. See [Logging in to Afnio Backend](login) 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** ```python 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. ``` ```python # 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](automatic_differentiation) 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: ```python 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:_ ```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](../../generated/afnio.cognitive.functional) and [Modular API](../../generated/afnio.cognitive.modules) 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** ```python # 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:_ ```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)`. ```python # 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:_ ```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. ``` ```python # 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:_ ```output [4, 5, 6] requires_grad before: False requires_grad after: True ``` --- ## Automatic Differentiation Variables can track operations for gradient-based optimization. ```python # 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:_ ```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: ```python x = Variable("123") y = x.to(int) print(x.data, type(x.data)) print(y.data, type(y.data)) ``` _Output:_ ```output 123 123 ``` --- ## Bridge with Python Numbers Lists and Strings Variables can be initialized from and converted to Python numbers, lists, strings, or numbers. ```python 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)) ``` ```output [1, 2, 3] hello 42 ``` --- ## 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.