Skip to content

FunctAIFunc

FunctAIFunc(
    fn,
    *,
    tools=None,
    template=None,
    messages=None,
    module_kwargs=None,
    examples=None,
    requires=None,
    **cfg,
)

A typed Python function whose body is a model call. Build with @ai.

Calling it runs the model and gives the answer, typed as the function's return type; predict gives every output; acall and apredict are the same in async code (an async def AI function is awaited directly).

Attributes

Name Description
adapter How values are written into the prompt and read back: a name ("xml", "chat",
debug Whether each call prints what it sends and what comes back.
demos The worked examples sent before every call (input and answer pairs), usually chosen by
instructions The instruction the model gets: an optimized one, or the one written from the code.
interface What a caller gives and gets, as data (contract/programs.md): the
lm The model this function's own settings name (@ai(lm=...), fn.lm = ...); None when it
module How the model answers: "predict" (the default) or "cot" (it writes its reasoning
optimizer The optimizer fn.opt(rows) uses when none is given (default: BootstrapFewShot).
signature The lmcc signature: inputs, outputs, instruction.
temperature The sampling temperature this function's own settings name; None when it uses the one
template The chat template the function writes its conversation with
tools The tools the model may call (a copy of the list). Setting it replaces them.
trials What the search that made this copy tried (GEPA's candidates,
version The function's version: a fingerprint of what it sends besides its inputs.

Methods

Name Description
acall await fn.acall(...): the answer, in async code. The call runs in a
apredict await fn.apredict(...): predict in async code (every output, the usage and the
bake Train weights that answer this function; returns the baked model.
conversation A conversation with this function: each call a turn that sees the
explain How calls are laid out for the current model: adapter, reader, transports, formats.
freeze Stop automatic instruction refinement for this function, now.
load Read an instruction and demos written by fn.save(path) and use them, in place.
load_state Use this instruction and these demos (a ProgramState, or its dict from
map Run on every row of a table, and return the run table.
opt An improved copy: its instruction and worked examples chosen from rows
optimization_runs How this function was improved, oldest first: the optimizer, the
plan The lmcc plan for the current model: .explain(), .describe(), .render(...).
predict The call, with everything it produced: every output (p.result,
render The exact request the next call would send, without sending it.
save Write the instruction and demos to a JSON file (load reads it back).
state The instruction and demos in use.
stream Call the function and watch the answer being written.
to_dspy Removed: FunctAI no longer runs on DSPy, so there is no DSPy program to give.
unpack One column per field of the answer, to spread into a table.
using A copy of this function with other settings or another layout.
vectorize This function as a column expression, with options.

acall

FunctAIFunc.acall(*args, **kwargs)

await fn.acall(...): the answer, in async code. The call runs in a worker thread, so the event loop is free while the model answers.

apredict

FunctAIFunc.apredict(*args, **kwargs)

await fn.apredict(...): predict in async code (every output, the usage and the call's id), run in a worker thread so the event loop is free while the model answers.

bake

FunctAIFunc.bake(data, **options)

Train weights that answer this function; returns the baked model. fast = fn.using(lm=baked) runs the function on them. See functai.bake.bake for the options (student, teacher, labels, test, ...).

conversation

FunctAIFunc.conversation(
    id=None,
    *,
    store=None,
    context=None,
    earlier_without=(),
    sends='queue',
    **settings,
)

A conversation with this function: each call a turn that sees the earlier ones, kept in store; the function itself is unchanged.

Parameters

Name Type Description Default
id str The conversation's id; the same id in the same store opens it again (tomorrow, in another process). Default: a new one. None
store None, True, folder, or store None: this process's memory. A folder (or True: the default one) keeps it across runs. Any object with append and read (functai.stores). None
context optional Which earlier turns the model sees: every one (default), or functai.last_turns(10); without=[...] leaves bulky inputs out of earlier turns. None
earlier_without list of str Outputs this function now writes that earlier turns lack (reasoning after turning on module="cot"). ()
sends str Two sends at once: "queue" (default), "refuse" or "branch". 'queue'
**settings Any Settings for every turn (approve, lm...). {}

Returns

Name Type Description
Conversation Called like the function. chat.turns, chat.render(...), chat.continue_from(turn), chat.stream(...).

Examples

import functai
from functai import *
@ai
def tutor(message: str) -> str:
    """Tutor a student in fractions, one small step at a time."""
    ...

chat = tutor.conversation("alex")
chat("Hi, I'm Alex.")
chat("What is 1/2 + 1/3?")
[t.inputs["message"] for t in chat.turns[-1].saw]
functai: no model chosen, so using gpt-4.1-mini (environment ($OPENAI_API_KEY)). Choose one with functai.configure(lm=...).
["Hi, I'm Alex."]

explain

FunctAIFunc.explain()

How calls are laid out for the current model: adapter, reader, transports, formats.

freeze

FunctAIFunc.freeze()

Stop automatic instruction refinement for this function, now.

Only matters when refinement was turned on (@ai(instruction_autorefine_calls=n)): the first n calls then ask a model to rewrite the instruction from what it saw. freeze() keeps the instruction as it is from here on, which you want before evaluating, saving or comparing versions. It changes the function in place and returns it.

load

FunctAIFunc.load(path)

Read an instruction and demos written by fn.save(path) and use them, in place. Returns the function. (To save a whole program with everything it depends on: functai.save.)

load_state

FunctAIFunc.load_state(state)

Use this instruction and these demos (a ProgramState, or its dict from state().to_dict()), in place. Returns the function.

map

FunctAIFunc.map(data, *, threads=None, num_threads=None, progress=None)

Run on every row of a table, and return the run table.

evaluate without the scoring: the rows, the predictions (pred_<output>), and each row's error, seconds, tokens and model. A row that fails keeps its error; the others go on. Needs pip install "functai[data]".

For long runs, keep replies on disk (functai.configure( cache_replies="disk")): running map again after an interruption, or to retry the rows that failed, sends only what has no kept reply.

Parameters

Name Type Description Default
data list of dict, or a table Anything dpyr.read() takes; columns named like the parameters are the inputs. required
threads int How many rows run at once (default 1). num_threads is the same. None
progress bool A line on stderr, updated as rows finish: rows done, errors, tokens, time left. Default: on in a terminal or a notebook. None

Returns

Name Type Description
dpyr dataframe

See Also

  • FunctAIFunc.vectorize: the function as a column expression.

Examples

@ai
def capital(country: str) -> str:
    """The country's capital city."""
    ...

capital.map([{"country": "Norway"}, {"country": "Ghana"}], threads=2, progress=False)

opt

FunctAIFunc.opt(data=None, *, optimizer=None, metric=None, valset=None, **opts)

An improved copy: its instruction and worked examples chosen from rows with known answers. This function is unchanged.

Only what the function sends besides its inputs changes: the instruction and the demos. Code, types and layout are never touched. functai.labeled_few_shot, functai.bootstrap_few_shot and functai.gepa are the common cases, by name.

Parameters

Name Type Description Default
data list of dict, or a table Rows as for evaluate: columns named like the parameters are the inputs, the others the expected outputs. None
expected str or dict The column holding the right answers, as for evaluate: expected="category". required
optimizer optimizer class or instance Default BootstrapFewShot. See the Optimizers section. None
metric function or dpyr expression As for evaluate. Default: exact match on the expected outputs. None
valset list of dict, or a table Rows for optimizers that choose between candidates. None
teacher_lm str A stronger model that runs the examples; its good runs become demos. required
teacher AI function Or a teacher function. required
n_synth int With a teacher: first write this many training rows. required
**opts Passed to the optimizer. {}

Returns

Name Type Description
FunctAIFunc The improved copy, with its own version.

See Also

Examples

from typing import Literal

@ai
def category(message: str) -> Literal["shipping", "billing", "product"]:
    """The support category of the message."""
    ...

train = [
    {"message": "The vase came smashed.", "result": "shipping"},
    {"message": "Money back please, the chair wobbles.", "result": "billing"},
    {"message": "The handle came off after two uses.", "result": "product"},
]
taught = category.opt(train)
[d.inputs["message"] for d in taught.demos]

optimization_runs

FunctAIFunc.optimization_runs()

How this function was improved, oldest first: the optimizer, the examples, what changed (and, for a search, its trials).

plan

FunctAIFunc.plan()

The lmcc plan for the current model: .explain(), .describe(), .render(...).

predict

FunctAIFunc.predict(*args, **kwargs)

The call, with everything it produced: every output (p.result, p.reasoning...), the tokens, the model's replies, the call's id.

Examples

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

p = solve.predict("3 pencils cost $1.20. How much do 10 cost?")
p.result, p.reasoning

render

FunctAIFunc.render(*args, **kwargs)

The exact request the next call would send, without sending it.

Parameters

Name Type Description Default
*args The call's inputs, as for calling the function. ()
**kwargs The call's inputs, as for calling the function. ()

Returns

Name Type Description
lm15.Request .system, .messages, .tools, .config, .model.

See Also

Examples

@ai
def capital(country: str) -> str:
    """The country's capital city."""
    ...

request = capital.render("Chile")
print(request.system)
print(request.messages[0].parts[0].text)

save

FunctAIFunc.save(path)

Write the instruction and demos to a JSON file (load reads it back).

state

FunctAIFunc.state()

The instruction and demos in use.

stream

FunctAIFunc.stream(*args, **kwargs)

Call the function and watch the answer being written.

The call starts at once, in the background, and is the same call as fn(...): the same retries, tools and call log line, the same value in the end. Iterate the stream for the answer's text as it arrives.

Parameters

Name Type Description Default
*args The call's inputs, as for calling the function. ()
**kwargs The call's inputs, as for calling the function. ()

Returns

Name Type Description
Stream for piece in s (or async for): the answer's text, piece by piece. s.result: the value (waits); await s in async code. s.events(): everything, with the reasoning, tool calls and retries. s.text, s.partial: the answer so far. s.close(): stop the call.

See Also

Examples

@ai
def haiku(topic: str) -> str:
    """A haiku about the topic."""
    ...

for piece in haiku.stream("the first snow"):
    print(piece, end="", flush=True)

Everything the model writes, reasoning first:

@ai
def solve(problem: str) -> float:
    """Solve the word problem."""
    reasoning: str = _ai["Step by step."]
    return _ai

s = solve.stream("3 pencils cost $1.20. How much do 10 cost?")
for event in s.events():
    if event.kind == "text":
        print(event.text, end="", flush=True)
s.result

to_dspy

FunctAIFunc.to_dspy(deepcopy=False)

Removed: FunctAI no longer runs on DSPy, so there is no DSPy program to give.

Always raises NotImplementedError. What an optimizer found is fn.state() (the instruction and the worked examples), which fn.save(path) writes as JSON; a whole program, with everything it depends on, is saved with functai.save.

unpack

FunctAIFunc.unpack(*args, threads=None, errors='raise', prefix='', **kwargs)

One column per field of the answer, to spread into a table.

For a function whose answer is a record (a dataclass, a pydantic model, a TypedDict): table.mutate(**fn.unpack(col.text)) adds one column per field. Each distinct row still costs one model call.

Parameters

Name Type Description Default
*args Any The inputs, as columns (col.note) or constants. ()
**kwargs Any The inputs, as columns (col.note) or constants. ()
threads int How many rows run at once (default 8). None
errors str "raise" (default) or "null", as for vectorize. 'raise'
prefix str Put before each column's name (prefix="ai_" → ai_species). ''

Returns

Name Type Description
dict {field: column expression}, for mutate(**...).

See Also

  • FunctAIFunc.vectorize: the whole answer as one column.

Examples

from dataclasses import dataclass
from dpyr import read, col

@dataclass
class Contact:
    name: str
    city: str | None   # None when the text does not say

@ai
def contact(text: str) -> Contact:
    """The person the text is about."""
    ...

people = read([{"text": "Ada Lovelace wrote to us from London."},
               {"text": "Grace Hopper called."}])
people.mutate(**contact.unpack(col.text))

using

FunctAIFunc.using(template=_KEEP, **settings)

A copy of this function with other settings or another layout.

The copy starts with the same instruction and demos; the original is untouched. A setting given as None is no longer set by the copy: it comes from configure or the defaults. An adapter replaces the template, and a template replaces the adapter.

Parameters

Name Type Description Default
template list A chat template for the copy. _KEEP
**settings Any setting @ai takes: lm, temperature, adapter, client, tools... {}

Returns

Name Type Description
FunctAIFunc The copy.

Examples

@ai
def capital(country: str) -> str:
    """The country's capital city."""
    ...

capital.using(lm="gpt-4.1-nano")("Chile")

vectorize

FunctAIFunc.vectorize(dtype=None, threads=None, errors='raise')

This function as a column expression, with options.

Calling an AI function on a dpyr column (fn(col.text)) is the same with the defaults. Each distinct input is sent once; answers are remembered for the session; the prompt in use now is the one the column is computed with.

Parameters

Name Type Description Default
dtype optional The column type. Default: the return annotation (text when there is none). None
threads int How many rows run at once (default 8). None
errors str "raise" (default): raise after every row ran; running again retries only the failures. "null": a failed row is null. 'raise'

Returns

Name Type Description
function Call it on columns: fn.vectorize(threads=16)(col.text).

Examples

from dpyr import read, col

@ai
def capital(country: str) -> str:
    """The country's capital city."""
    ...

read([{"country": "Norway"}, {"country": "Ghana"}]).mutate(
    capital=capital.vectorize(threads=2)(col.country))