Skip to content

Reasoning and several answers

Ask the model to think first, return several values, and get everything a call produced with predict.

import functai
functai.configure(lm="gpt-4.1-mini", temperature=0)   # the model behind every output on this page
from functai import ai, _ai

A function returns one value, but a model can write more than one thing before it answers: its reasoning, a draft, a confidence. In functai each of those is a variable assigned from _ai.

One more output

Assign _ai to a variable and it becomes an output with that name; a comment on the line describes it to the model. Outputs are written in the order you declare them, and the answer comes last, so an output declared first is written before the answer.

@ai
def solve(question: str) -> float:
    """Solve the word problem."""
    reasoning: str = _ai    # step by step, the calculation that gives the answer
    return _ai

solve("A train travels 120 miles in 2 hours, then 90 miles in 1.5 hours. "
      "What is its average speed in miles per hour?")
60.0

The call returned only the answer. The reasoning was written, and used by the model, but not returned.

Everything: predict

fn.predict(...) makes the same call and gives a Prediction with every output by name, plus what the call cost:

p = solve.predict("A train travels 120 miles in 2 hours, then 90 miles in 1.5 hours. "
                  "What is its average speed in miles per hour?")
p.reasoning
'First, find the total distance traveled by the train:\n120 miles + 90 miles = 210 miles\n\nNext, find the total time taken:\n2 hours + 1.5 hours = 3.5 hours\n\nNow, calculate the average speed by dividing the total distance by the total time:\nAverage speed = Total distance / Total time = 210 miles / 3.5 hours = 60 miles per hour'
p.result, p.usage["input_tokens"], p.usage["output_tokens"]
(60.0, 98, 103)

dict(p) gives the outputs as a dictionary, and p.turn the whole exchange (every model and tool step).

Reasoning without declaring it: module="cot"

@ai(module="cot") asks for reasoning before the answer without changing the function. Models with a thinking channel of their own (OpenAI o-series and GPT-5, Claude 4, Gemini 2.5) use it; the others write a reasoning section first.

@ai(module="cot")
def solve_cot(question: str) -> float:
    """Solve the word problem."""
    ...

solve_cot("If 3 pencils cost $1.20, how much do 10 pencils cost?")
4.0

Several values back

To return several values, declare each as an output and return them together:

@ai
def critique_and_improve(text: str) -> tuple[str, str]:
    """Criticize the text constructively, then improve it."""
    critique: str = _ai     # what is unclear or rude, in one sentence
    return critique, _ai

critique, improved = critique_and_improve("U should fix this asap, it's broken.")
print(critique)
print(improved)
The text is too informal and vague, lacking specific details and polite tone which can come across as rude or demanding.
The text is too informal and vague, lacking specific details and polite tone which can come across as rude or demanding.

Or return a record (a dataclass), which names each part; see Types.

Here critique is a named output and the bare _ai is the answer (the improved text): a bare _ai is always the answer.

Using an output in the body

The body runs after the model answers, so any output can be checked or combined in plain Python before it is returned:

from typing import Literal

@ai
def confident_label(text: str) -> str:
    """Is the review positive or negative?"""
    label: Literal["positive", "negative"] = _ai
    confidence: float = _ai   # how sure you are, from 0 to 1
    return label if confidence >= 0.7 else "unsure"

confident_label("It was fine, I guess. Not what I expected.")
'negative'

Descriptions in brackets

Where a comment won't fit (a long description, a line already commented), the description can go in brackets: critique: str = _ai["What is unclear or rude, in one sentence."]. It means exactly the same thing.