Ship it: save, verify, load¶
Find everything a program depends on, save it to a folder, prove it runs in a fresh environment, and load it back.
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 functai program is code plus a contract: its inputs and outputs are typed, and to run it somewhere else, everything it depends on must come along. The AI functions and their optimized prompts, the tools, your helper functions and classes, constants, data files, and the exact packages. functai reads the code to find all of it.
The steps are always the same:
check(program)shows what it depends on, and what would stop a clean save;save(program, folder)writes it all to a folder you can commit;verify(folder)proves it works in a brand-new environment;load(folder)brings it back, anywhere.
A program to save¶
from dataclasses import dataclass
from enum import Enum
from functai import module
class Priority(Enum):
LOW = "low"
URGENT = "urgent"
@dataclass
class Handled:
priority: Priority
reply: str
ORDERS = {"A-1042": "stuck at carrier", "B-2210": "delivered"}
def lookup_order(order_id: str) -> str:
"""The order's shipping status."""
return ORDERS.get(order_id, "no such order")
@ai
def priority(message: str) -> Priority:
"""How urgently the message needs an answer."""
...
@ai(tools=[lookup_order])
def draft_reply(message: str) -> str:
"""A short, friendly reply. Check the order first when one is mentioned."""
...
@module
def handle(message: str) -> Handled:
return Handled(priority(message), draft_reply(message))
Check¶
functai.check(handle)
handle @module [__main__]
├── Handled class [__main__]
│ ├── Priority class [__main__]
│ │ └── Enum (stdlib)
│ └── dataclass (stdlib)
├── priority AI function (message: str → Priority) [__main__]
│ └── Priority (see above)
└── draft_reply AI function (message: str → str) [__main__]
└── tool lookup_order function [__main__]
└── ORDERS = {'A-1042': 'stuck at carrier', 'B-221...
requirements: functai @ file:///home/maxime/Projects/functai/python
! local-install requirements: installed from folders on this machine: functai (/home/maxime/Projects/functai/python), lmcc (/home/maxime/Projects/lmcc/python)
fix: the saved program loads where those folders exist; publish them, or install released versions, to load it anywhere
Why this page shows
local-installThis site is built from the development copies of functai and lmcc, installed from folders, socheckwarns that the saved program needs those folders. It is the warning you would get for any package of yours installed that way. With functai installed from PyPI, the requirement readsfunctai==<version>and there is no warning.
check follows every name the code reaches: AI functions and modules
(also through helper functions), their tools, your own functions and
classes (in files or notebook cells), the types in signatures, constants,
and data files read through functai.file("...").
| found | saved as |
|---|---|
an AI function or @module |
its code, settings, instruction and examples |
| a function or class from your code | its source, verbatim |
| a name from an installed package | a pinned requirement |
| the standard library | nothing |
| a constant (numbers, text, lists, dicts, enum members, patterns) | its value |
a file read with functai.file("data/x.txt") |
a copy |
And what stops a clean save, each with its fix:
| problem | example | fix |
|---|---|---|
hidden-state |
a tool writes CACHE[q] = ... into a global |
pass it in and return it, or save(allow=["hidden-state"]) to save its current value |
untyped-input, untyped-output |
def f(text): |
annotate it |
unsaveable-value |
a global client, lock or open file | create it inside the function, or pass it in |
lambda, no-source, name-conflict |
a lambda tool; two nested def f |
a named def; distinct names |
local-import-inside |
import helpers inside a function |
import it at the top |
Save¶
import tempfile, os
folder = os.path.join(tempfile.mkdtemp(), "support_desk")
functai.save(handle, folder, record=[{"message": "Where is order A-1042?"}])
for root, dirs, files in sorted(os.walk(folder)):
for f in sorted(files):
print(os.path.relpath(os.path.join(root, f), folder))
functai.json
recordings.json
requirements.lock
requirements.txt
code/main.py
The folder is plain files, readable and diffable:
| file | holds |
|---|---|
functai.json |
each AI function's settings, instruction, examples, signature and fingerprints; file hashes |
code/*.py |
the code the program reaches (a notebook becomes code/main.py) |
files/ |
data files read with functai.file(...) |
requirements.txt, requirements.lock |
the packages, pinned; and everything they pull in |
recordings.json |
model replies recorded with save(record=...), for verify |
save refuses while check finds errors, and writes the folder whole or
not at all. Keys and connections are never saved: the loading machine
uses its own.
Verify¶
verify is the proof. It builds a new environment with uv from the
lock file alone, loads the program there from an empty folder (so nothing
from your project can leak in), and checks that every AI function sends
byte-identical requests, and that each recording replays to the same
result. No model is called.
functai.verify(folder, trust=True, fresh=False)
verified in this environment
(fresh=False checks in the current environment, which is quicker and
weaker; the default builds the fresh one, about a second once uv's cache
is warm.)
Load¶
loaded = functai.load(folder, trust=True)
loaded("My toaster order B-2210 never arrived??")
Handled(priority=<Priority.URGENT: 'urgent'>, reply="Your toaster order B-2210 shows as delivered. Could you please check around your delivery area or with neighbors? Let me know if you still can't find it, and I'll help you further.")
load checks before it runs anything: file hashes (catching accidental
edits), missing packages, and afterwards that every AI function still
sends the requests it sent when saved.
trust=Truemeans "run this code"loadandverifyrun the saved Python code, so they requiretrust=True. The hashes catch accidents, not someone who edits both the code andfunctai.json. Load only folders you would run as code.
What reading code can't see¶
Names looked up while running (getattr, importlib, eval), functions
passed in as arguments, and files not read through functai.file.
check points at them. @ai(requires=["numpy>=2"]) or
save(requires=[...]) declares packages by hand;
save(include=["myproject"]) saves an editable-installed project as
code; and verify with recordings catches anything still missing.