Build it yourself, or with an engineer from the team. Get started.

// the llm ops platform

Trust your agents in production.

Build your prompt or agent as an Agentic Function. Functional AI evaluates it, runs it, and traces every call, and you ship new versions anytime, straight from your code.

Both on every plan. SDKs on GitHub

app.py
# your code
from fnai import Function
classify = Function("classify", api_key="fnk_…")
result = classify.run(subject=subject, body=body)
# typed result
{
"queue": "billing",
"priority": 2,
"reason": "Charged twice for the March invoice."
}
v3 · 1840ms · $0.0021 · 908 tokens

Most LLM ops tools hand you a dashboard to operate. Functional AI evaluates, runs and traces the agent, and hands you a function you can trust in production.

// the problem

Shipping AI is easy. Trusting it in production is not.

It breaks and nobody notices

A prompt tweak quietly regresses. You find out when a customer does, not when it shipped.

The bill creeps and nobody knows why

A chattier model, a bigger prompt, an agent that loops one step too many. Spend climbs, and nobody can say what one run actually costs.

It does something it shouldn't

An agent issues a refund before anyone approved it, or fires the same tool twice. You find out from the customer.

Your software is reliable. Your AI should be too.

// the platform

You shouldn't have to babysit AI in production.

Functional AI is the LLM ops platform for prompts and agents: evaluation, runtime and observability in one place. You write the Agentic Function; we run everything behind it. No platform team required.

your code

result = classify.run(subject=subject, body=body)
Functional AI runs everything below

Hosting

No servers to run. Your Agentic Function lives on our infrastructure.

Your own keys

Amazon Bedrock, Anthropic, OpenAI or Google. Your provider bills the tokens; we never mark them up. No lock-in.

Files in

Attach knowledge files your agent can read. Callers send PDFs and images, passed to the model natively.

Evaluation

No eval harness to build. Measure every version before you ship it, then decide what goes live.

Tools

Any REST API or MCP tool. Test it against the real endpoint before you save it, then add guards and call limits.

Versioning

No redeploys. New versions go live behind the same function call.

Tracing

Every call traced: input, output, each tool call, latency and cost.

Cost per run

Compare models side by side, with the cost of one run on each, before you pick one.

All the agents, none of the infrastructure.

// how it works

Three steps to AI you can trust.

No platform team. No eval pipeline to build. No infrastructure to run. Write it, test it, and call it.

  1. 01

    Write it

    Your prompt or agent as an Agentic Function, with a typed input and output. In the canvas, a YAML file, the SDKs, or from your coding agent over MCP.

  2. 02

    Test it

    Run it over a dataset on several models at once. Read the trust score, the cost per run, and the findings, each with a fix for the prompt.

  3. 03

    Deploy & call it

    We host it. Ship new versions with no app redeploy, and roll back instantly. The URL and keys never change.

// two ways to start

Build it yourself. Or build it with us.

Self-serve

Your engineers, the docs, the SDKs and MCP. Define the Agentic Function, run the experiments, deploy when the trust score says so.

Every plan

Forward deployed engineering

An engineer from the team that built the platform gets one agent to production with you, or improves one you already run: tools, the first dataset and Score Set, experiments until it is ready, deploy with your team holding the keys, then hands over.

Every plan, priced per engagementTalk to an engineer

When our engineer leaves, nothing breaks: every version, dataset and experiment is in your account.

// write

Three ways in. One Agentic Function out.

Build in the browser, in code, or from your coding agent. However you start, you end up with the same definition: a typed Agentic Function we host and you call. Each version records which way it came in.

Canvas

Build and test an Agentic Function in the browser, step by step. No setup.

SDK & YAML

Write the definition in Python, TypeScript or Go, with every line checked as you write it. Or keep it in a YAML file and push it with the CLI.

$ fn new function.yaml

MCP

Your AI coding agent builds and tests over MCP, with your permissions. There is no deploy tool: a human ships.

// evaluate & iterate

Test it. Tune it. Until the output and the cost are right.

Run a version over a dataset on several models at once. Each model gets a trust score, a pass rate, cost per run and p95 latency, and every finding comes with a fix for the prompt, not just a number. You read the results and decide what ships.

experiment · v3 on tickets (200 rows)
$ fn eval 42 --version 318 --snapshot 7 --scores 5
claude-sonnet-4-6
trust 91 strong · pass 94% · $0.0021/run · p95 1.9s
claude-opus-4-8
trust 93 strong · pass 96% · $0.0098/run · p95 3.4s
stability: queue 3/3 · priority 3/3 · reason varies
finding "account" tickets routed to "billing" (11 rows)
fix add a definition of "account" to the flow

Quality

Score against datasets of real inputs and expected outputs. Enum outputs get a confusion matrix.

Consistency

Repeat every run. Stability is measured field by field, with no labels needed.

Model choice

Several models in one experiment, each with its cost per run, before you pick one.

Compared to what's live

A drop against the deployed version is flagged before you deploy. You decide.

How the evaluation platform works →

// agent tests

Your agent's definition writes its own safety tests.

A right answer can still come from the wrong path: a refund before approval, a tool called twice. Every guard, call limit and effect you declare becomes a behaviour check, run in every experiment, so the definition is proven before it touches a real order.

refund_agent.yaml
tools:
  - name: lookup_order
    max_calls: 3
  - name: human_approval
    config: { prompt: "Approve this above-limit refund?" }
  - name: issue_small_refund
    config: { max_amount: 100 }
    max_calls: 1
    effect: side_effecting
  - name: issue_refund
    requires: "human_approval.returned.approved == true"
    effect: side_effecting
generated checks
  • ✓ issue_refund never called before human_approval returns approved

    from requires

  • ✓ issue_small_refund never called twice in a run

    from max_calls: 1

  • ✓ lookup_order called at most 3 times

    from max_calls: 3

  • ✓ No side-effecting call repeated with the same arguments

    from effect: side_effecting

Tools are mocked per test row. An agent that issues refunds can be tested a thousand times without issuing one.

// deploy & call

Deploy a version. Then just call it.

Deploy a version and you get a stable, authenticated HTTP endpoint, hosted on our infrastructure. Send inputs, get typed output, a status, and the trace ID and metadata you need to bill, trace, and debug.

app.py
from fnai import Function
classify = Function("classify", api_key="fnk_…")
result = classify.run(subject=subject, body=body)
200 OK
{
  "output": {
    "queue": "billing",
    "priority": 2,
    "reason": "Charged twice for the March invoice."
  },
  "status": "success",
  "meta": {
    "version": 3,
    "latency_ms": 1840,
    "cost_usd": 0.0021,
    "tokens_in": 812,
    "tokens_out": 96,
    "trace_id": "tr_8f3c2a1d"
  }
}

Four statuses, no guessing

successpartialescalatedfailed

An agent out of steps returns its best answer as partial. One that asks a human returns escalated. Your code branches on the status.

Bad input costs nothing

Input that breaks the contract is rejected with a 422 naming the field, before any model call and before any tokens.

// observe

Every call, every step, and a button that turns a failure into a test.

Each production call is recorded step by step, by version and by key. When one goes wrong you see exactly which tool did what, and every production failure becomes a test in two clicks.

trace · refund_agent v4 · tr_7c1e09
input order_id ORD-104233 · amount 240.00
step 1 lookup_order 210ms ok
step 2 human_approval — amount 240.00 > 100
status escalated · 2 steps · 2.6s · $0.0034
run stopped for approval; re-run with the decision in context
⚑ Flag → dataset added to refund-edge-cases as row 48

Every call traced

Input, output, status, model, cost and tokens, plus every step: each tool call with its arguments, result, latency and any error.

Attention filter

One filter for the calls that need a look: failed, throttled, partial and escalated.

Flag to test

A bad production call becomes a dataset row in two clicks, so the next version is tested against it.

// reliability

When a tool fails, the agent degrades. It doesn't guess.

Real tools and networks fail. Functional AI bounds every run, retries only what is safe to retry, and when an agent can't finish, it says so with a status instead of inventing an answer.

Retries by effect

Only read-only and idempotent tools are retried. A side-effecting call, a charge or a send, is never fired twice.

Bounded loops

Every agent run has a step budget, 12 by default, and max_calls locks a tool when its cap is hit. Out of steps, you get status partial with the reason.

Tool failures are information

A failed tool call goes back to the agent as an error, not a crash, so it can try something else. Every tool call times out after 30 s.

Escalation

When an agent calls human_approval, the run stops with status escalated. You collect the decision and re-run with it.

// security

Agents touch the outside world. We contain the blast radius.

The moment an Agentic Function fetches a web page or calls a tool, it has an attack surface. We won't claim prompt injection is solved; it isn't. So Functional AI limits what a manipulated agent can reach: tools it cannot see, credentials it never holds, networks it cannot call.

Guarded tools are invisible

A tool with requires is hidden from the model until its condition holds. Safety comes from the runtime, not the prompt.

No credentials in the runtime

Tool credentials live in a gateway, never where the agent runs. There is nothing in the runtime worth stealing.

Private network refused

Loopback, private and cloud-metadata addresses are refused for every tool call, platform or your own.

Sensitive fields redacted

Fields marked sensitive are redacted from traces before they are stored.

// ship new versions anytime

Ship a better version at 2pm. No redeploy.

The same function call picks up the new version behind it, so you ship improvements without redeploying your app. What you tested is what runs: each version compiles to one hashed artifact. Diff any two, and roll back instantly when production tells you to.

fn diff 317 318
$ fn diff 317 318
implementation.flow:
+ "Login, password and SSO problems are account, not billing."
output.priority:
- maximum: 5
+ maximum: 4
Draft ▸ Ready ▸ Live
  • DraftEdited in place until you're happy.
  • ReadyIts Contract Test passed. Testable, deployable.
  • LiveDeployed, and frozen for good. Changes make a new version.

instant rollback

$ fn rollback 42 --to-version 317

No rebuild. The URL and keys never change.

// with Functional AI

Two ways to run AI in production.

The difference between hoping your AI holds up and knowing it does.

Without Functional AI

  • Prompts scattered across Notion, env vars, and your codebase
  • You find out it broke from a customer
  • Agent behaviour tested in production, on real orders
  • Costs creep up and nobody knows what one run costs
  • Locked into one model; a price hike or deprecation is your problem
  • Changing the AI means a full redeploy

With Functional AI

  • Measured against the last version before you ship
  • Agent behaviour tested without touching real systems
  • One Agentic Function you call from your code
  • Roll back in one command
  • Your own model keys, no markup
  • Ship new versions anytime, no redeploy

No platform team required. Your engineers own the agent end to end.

// faq

Common questions

What is the best way to host an agent as an Agentic Function?
Define it once, in the canvas, in a YAML file pushed with the fn CLI, or with the Python, TypeScript or Go SDK, as a Prompt function (one model call, typed in and typed out) or an Agent (loops with tools until its answer satisfies the output contract, within a step budget). Functional AI hosts each deployed version behind a stable HTTP endpoint: your app sends the input with an invoke key and gets typed output, a status, and metadata back. Input that breaks the contract is rejected with a 422 naming the field, before any model call. Shipping a new version never needs an app redeploy.
How do I test an agent before it touches real systems?
Run an experiment over a dataset with the agent's tool responses mocked or replayed per row, so no test ever reaches a real system. Functional AI generates behaviour checks from the agent's own definition: issue_refund is never called before human_approval returns approved, a tool capped at max_calls: 1 is never called twice, and no side-effecting call is repeated with the same arguments. Evaluators for tool selection, tool arguments, path, step efficiency, task completion and escalation sit alongside them. An agent that issues refunds can be tested a thousand times without issuing one.
How do I add a human approval step to an agent?
Give the agent the built-in human_approval tool and guard the risky tool with requires, for example requires: "human_approval.returned.approved == true". A guarded tool is invisible to the model until its condition holds, so the safety comes from the runtime, not the prompt. When the agent calls human_approval, the run stops with status escalated; your app collects the decision and re-runs with it in the context.
How do I choose the best model for an agent?
Run one experiment across several models on the same version, dataset and Score Set. Each model gets a trust score out of 100, a pass rate, cost per run and p95 latency, plus findings that each come with a concrete fix for the prompt. You compare the results and pick the model when you deploy. The model is not part of the definition, so changing it later is a new deploy, not a code change. Calls run on your own Amazon Bedrock, Anthropic, OpenAI or Google key, and we never mark tokens up.
How do I ship a new prompt version without redeploying my app?
Your app calls the same Agentic Function endpoint with the same invoke key every time. Edit the definition, test the new version, and deploy it from the app or with fn deploy. The endpoint starts serving the new version and your code does not change. Each version compiles to one hashed artifact, so what you tested is what runs.
How do I version and roll back prompts in production?
Every Agentic Function has versions that move from Draft to Ready (once its Contract Test passes) to Live (once deployed). The first deploy freezes a version for good, and fn diff shows what changed between any two. To roll back, use fn rollback or the button in the app to return to the previous or any earlier live version. Rollback is instant, needs no rebuild, and the URL and keys never change.
Can you build the agent for us?
Yes. Forward deployed engineering is available on every plan, including Pro, and priced per engagement with a fixed scope: get one agent to production, or improve an existing agent. An engineer from the team that built the platform registers your tools, writes the first dataset and Score Set, builds and iterates until the experiments say it is ready, deploys with your team holding the keys, and hands over. Every version, dataset and experiment is in your account, so your engineers own it from there. When our engineer leaves, nothing breaks. Enterprise plans include engineering hours.

Build agents you can trust. Start today.

Tell us what you want to ship and whether you want to build it yourself or with an engineer. We set up your account and reply.