Skip to main content

verbai

Visibly Enhanced Reasoning Blocks for AI. A Haskell-flavored DSL for AI model orchestration.

verbai is a tiny scripting language and PyPI package for composing and orchestrating AI model calls — local and cloud, text and multi-modal. It is text-based, has a hybrid declarative + procedural syntax, and treats every integration as a pluggable provider: Ollama, OpenAI, Anthropic, MiniMax, Gemini, Mistral, xAI, DeepSeek, Cohere, HuggingFace, OpenRouter, AWS Bedrock, Azure OpenAI, Vertex AI, Groq, Together.ai, Fireworks.ai, Replicate, MCP servers, HTTP APIs, webhooks, WebSockets, and more.


Table of contents

  1. Quickstart
  2. Installation
  3. How verbai works — the mental model
  4. Provider setup guide
  5. Built-in verbs (stdlib)
  6. The example programs
  7. CLI reference
  8. Project layout
  9. Running the tests
  10. Glossary
  11. What's next
  12. License

Quickstart

pip install verbai
verbai run examples/01_hello.verbai

You should see:

hello, world

The examples/ directory ships inside the installed wheel — no git clone required. The package itself is pure-stdlib Python 3.10+; no third-party dependencies for the core.


Installation

pip install verbai                # base: Mock provider + MiniMax (cloud extra)
pip install verbai[cloud]         # base + the same MiniMax module (already bundled in v0.1)
pip install verbai[local]         # + local-runtime providers (v0.2)
pip install verbai[all]           # everything that ships

verbai[cloud] is the install target PyPI users should use. It pulls in the MiniMax provider out-of-the-box (no extra packages required — MiniMax is implemented with urllib.request only).

Verify the install

python -m verbai --version         # should print: verbai 0.1.0
python -m verbai run examples/01_hello.verbai

How verbai works — the mental model

A verbai script is plain text in a .verbai file. When you run verbai run my.verbai:

┌─────────────────────────────────────────────────────────────┐
│                       .verbai source                        │
│                  (text file you wrote)                      │
└───────────────────────┬─────────────────────────────────────┘
                        │ Lexer (src/verbai/lexer.py)
                        ▼
                ┌───────────────┐
                │ token stream  │  e.g.  Print  "hello, world"  EOF
                └───────┬───────┘
                        │ Parser (src/verbai/parser.py)
                        ▼
                ┌───────────────┐
                │   AST         │  CallExpr(Print, [PosArg("hello, world")])
                └───────┬───────┘
                        │ Interpreter (src/verbai/interpreter.py)
                        ▼
                ┌───────────────┐
                │ runtime values│  Print() invoked → stdout
                └───────────────┘

The interpreter walks the AST and runs each node:

  • let NAME = EXPR at module top-level → bind NAME to the evaluated value in the global scope.
  • do ... end → run the body top-to-bottom, each <- binding introducing a fresh child scope (immutable within itself).
  • Run { provider = "..." ; model = "..." ; input = "..." } → look up the named provider in the registry, call provider.run_inference(model, input, **kwargs), return the resulting Value.
  • Function calls (Print "hello, world" with explicit parens, or Print "x" if the heuristic allows) → look up the callee by name and invoke it with the evaluated arguments.

Two syntactic conventions worth memorising

  1. Calls are explicit-paren'd. Print (10 - 4) is one Print call. Print 10 - 4 is Print(10) - 4, which fails at runtime (you can't subtract from Nil). Wrap binary expressions in parens.
  2. Object literals use semicolons, lists use commas. { host = "x" ; n = 7 } is an object; [1, 2, 3] is a list. To pass an object/list as a single positional argument, wrap it in parens: Print ({ a = 1 }). Otherwise the { a = 1 } is parsed as a keyword-block call (f { a = 1 } → f(a=1)), used by Run.

Provider setup guide

Every provider is a Python class registered with verbai.providers.register_provider. v0.1 ships two: Mock (always available, no key, returns canned responses) and MiniMax (real API, key from env var). Other providers in the spec roadmap (Ollama, OpenAI, Anthropic, …) raise NotImplementedError in v0.1 and are wired in v0.2.

Mock provider (no setup)

The default. No configuration required.

do
    out <- Run { provider = "mock" ; model = "echo" ; input = "hi" }
    Print out
end

Output:

echo: 'hi'

The Mock provider:

  • Echoes back f"{model}: {input!r}" for every call.
  • Records every call in mock.calls (useful for test fixtures).
  • Can be configured to raise after N calls via error_after.
from verbai.providers.mock import MockProvider
m = MockProvider()
m.error_after = 2        # raise ProviderError starting at call #3
m.error_message = "rate limited"

MiniMax provider

Sign up at platform.minimax.io (or the China-region host if you're there). Get an API key from your dashboard.

Security: the key is never read from a file. You inject it as an environment variable. Disable it on the dashboard as soon as you're done testing.

# bash / gitbash
export MINIMAX_API_KEY='sk-cp-...'
verbai run examples/14_minimax_pong.verbai
unset MINIMAX_API_KEY
# PowerShell
$env:MINIMAX_API_KEY = 'sk-cp-...'
verbai run examples\14_minimax_pong.verbai
Remove-Item Env:MINIMAX_API_KEY

Default model is MiniMax-M3. To use another model, override model in your script:

do
    reply <- Run {
        provider    = "minimax"
        model       = "MiniMax-M3.1-Flash-Preview"
        input       = "Explain retrieval-augmented generation in 3 sentences."
        max_completion_tokens = 256
    }
    Print reply
end

Optional kwargs passed through to the provider:

kwarg effect
max_completion_tokens cap response length (default 256)
temperature sampling temperature
top_p nucleus sampling

Endpoint: POST https://api.minimax.io/v1/chat/completions (OpenAI-compatible).

Using a different base URL (China region, self-hosted, etc.)

Construct the provider directly:

from verbai.providers.minimax import MiniMaxProvider
from verbai.providers import register_provider

register_provider(
    MiniMaxProvider(
        base_url="https://api.minimaxi.com/v1",  # China-region host
        api_key="sk-cp-...",
    )
)

Or set the env var and pass a one-off base URL via your script.

Other providers (Ollama, OpenAI, Anthropic, …)

These are stubs in v0.1 — Run { provider = "ollama" ; ... } raises ProviderError("unknown provider: 'ollama'"). Wiring each is a single Python file in src/verbai/providers/. See PUBLISHING.md (maintainer-only) for the roadmap.

Adding your own provider

from verbai.providers import Provider, register_provider
from verbai.runtime import TextValue

class MyProvider(Provider):
    name = "my-provider"

    def run_inference(self, model, input, **kwargs):
        # Call your backend here.
        return TextValue(f"{model}: {input}")

register_provider(MyProvider())

Drop this into a script or module that's imported before verbai run. Your provider then works the same way the built-ins do:

do
    out <- Run { provider = "my-provider" ; model = "v1" ; input = "hello" }
    Print out
end

Built-in verbs (stdlib)

These names are bound in every script's global scope:

Verb Signature Notes
Print Print value or Print v1, v2, … sep = " ", end = "\n" Writes to stdout, returns Nil.
LoadFrom LoadFrom "path.verbai" as "Text" Reads a file; default as = "Text" returns its contents.
SaveTo SaveTo "path" data Writes data's display string to disk; auto-creates parent dirs.
Env Env "KEY" default = "fallback" Reads an env var; raises VerbError(NAME) if missing and no default.
Run Run { provider = "x" ; model = "y" ; input = "z" } Invokes the named provider's run_inference.
Fallback Fallback expr1 expr2 … Returns the first expr that doesn't raise ProviderError.
Retry Retry n expr backoff = "exponential" Re-runs expr up to n times on failure.
ForEach ForEach list (x -> body) or ForEach.Parallel list (x -> body) Iterates; sequential or parallel.
Return Return value Short-circuits the enclosing function.

How Run resolves a name

Run { provider = "minimax" ; model = "MiniMax-M3" ; input = "hi" } is sugar over minimax.run_inference(model="MiniMax-M3", input="hi"). The verb looks up provider in the registry (built-ins auto-register on first lookup; third-party providers via register_provider() at import time). The first time a provider is referenced, verbai lazy-imports its module — so you only load what you use.

How Print handles each type

Value Prints
Int 42 42
Float 3.14 3.14
Text "hi" hi
Bool True True
Nil Nil
[1, 2, 3] [1, 2, 3]
{ a = 1 } {a = 1}

The example programs

All 14 example scripts ship inside the installed examples/ directory. Each one is self-contained — copy-paste the body into a .verbai file on your machine and run it. None of them require an API key except example 14 (real MiniMax).

01 01_hello.verbai

Print "hello, world"
$ verbai run examples/01_hello.verbai
hello, world

02 02_constants.verbai

let PI = 3.14
let NAME = "verbai"
let MAX_TOKENS = 4096
Print PI
Print NAME
Print MAX_TOKENS
$ verbai run examples/02_constants.verbai
3.14
verbai
4096

03 03_variables.verbai

-- 03_variables.verbai
-- `let` at module scope is an immutable binding.
-- Inside a `do` block, `<-` shadows with a local value.

let global = 100

do
    local <- 50
    Print (global + local)
    Print local
end

Print global
$ verbai run examples/03_variables.verbai
150
50
100

04 04_expressions.verbai

-- 04_expressions.verbai
Print (1 + 2 * 3)
Print ((1 + 2) * 3)
Print (10 - 4)
Print (10 / 3)
Print ("Hello, " + "world!")
Print (5 == 5)
Print (5 < 10)
$ verbai run examples/04_expressions.verbai
7
9
6
3.3333333333333335
Hello, world!
True
True

Important: verbai calls are explicit-paren'd. Print (10 - 4) is unambiguous; Print 10 - 4 parses as Print(10) - 4 and fails at runtime. Wrap binary expressions in parens.

05 05_types.verbai

-- 05_types.verbai
-- Primitive types and how they print.

Print 42                                  -- Int
Print 3.14                                -- Float
Print "hello"                             -- Text
Print True                                -- Bool
Print False                               -- Bool
Print nil                                 -- Nil
Print ([1, 2, 3])                         -- List of Int
Print ({ name = "verbai" ; version = 0 }) -- Object
$ verbai run examples/05_types.verbai
42
3.14
hello
True
False
Nil
[1, 2, 3]
{name = verbai, version = 0}

Object literals use semicolon between entries; commas are for lists. To pass a list or object as a single argument, wrap it in parens.

06 06_conditionals.verbai

-- 06_conditionals.verbai
let x = 5
if x > 0 then
    Print "positive"
else
    Print "non-positive"
end

let y = -3
if y > 0 then
    Print "positive"
else
    Print "non-positive"
end
$ verbai run examples/06_conditionals.verbai
positive
non-positive

07 07_loops.verbai

-- 07_loops.verbai
ForEach [1, 2, 3, 4, 5] (x -> Print x)
$ verbai run examples/07_loops.verbai
1
2
3
4
5

08 08_functions.verbai

-- 08_functions.verbai
let double x = x * 2
let add3 a b c = a + b + c

Print (double 21)
Print (add3 1 2 3)
$ verbai run examples/08_functions.verbai
42
6

09 09_recursion.verbai

-- 09_recursion.verbai
let factorial n =
    if n <= 1 then
        1
    else
        n * (factorial (n - 1))
    end

Print (factorial 5)
Print (factorial 10)
$ verbai run examples/09_recursion.verbai
120
3628800

10 10_lists.verbai

-- 10_lists.verbai
let xs = [10, 20, 30, 40]
Print xs
Print (xs[0])
Print (xs[2])

ForEach xs (x -> Print (x * 2))
$ verbai run examples/10_lists.verbai
[10, 20, 30, 40]
10
30
20
40
60
80

11 11_objects.verbai

-- 11_objects.verbai
let config = { host = "api.example.com" ; port = 443 ; retries = 3 }
Print config.host
Print config.port

do
    msg <- Run { provider = "mock" ; model = "echo" ; input = "ping" }
    Print msg
end
$ verbai run examples/11_objects.verbai
api.example.com
443
echo: 'ping'

12 12_errors.verbai

-- 12_errors.verbai
do
    out <- try
        do
            x <- Run { provider = "mock" ; model = "fail-once" ; input = "boom" }
            Print x
        end
    catch err ->
        Print "recovered from error"
    end
    Print out
end

do
    msg <- Run { provider = "mock" ; model = "echo" ; input = "hello" }
    Print msg
end
$ verbai run examples/12_errors.verbai
fail-once: 'boom'
Nil
echo: 'hello'

The try ... catch err -> ... end form binds the error to err and runs the catch body. Use _ instead of err if you don't need the error value. The err binding also works as a kind filter (catch DivisionByZeroError -> ...).

13 13_first_inference.verbai

-- 13_first_inference.verbai
do
    result <- Run { provider = "mock" ; model = "echo" ; input = "hi" }
    Print result
end
$ verbai run examples/13_first_inference.verbai
echo: 'hi'

14 14_minimax_pong.verbai (requires real API key)

-- 14_minimax_pong.verbai
do
    reply <- Run { provider = "minimax" ; model = "MiniMax-M3" ; input = "Reply with exactly one short sentence." }
    Print reply
end

Run it:

export MINIMAX_API_KEY=sk-cp-...
verbai run examples/14_minimax_pong.verbai
unset MINIMAX_API_KEY

You should see a one-sentence reply from real MiniMax M3 printed to stdout.

Disable the API key on your MiniMax dashboard as soon as you're done testing. The MiniMax provider reads MINIMAX_API_KEY from your environment; verbai never stores it.


CLI reference

verbai --version                 # print version
verbai run <file.verbai>         # execute a script
verbai check <file.verbai>       # parse + structurally check, no execution
verbai fmt <file.verbai>         # format to stdout
verbai fmt <file.verbai> --write # rewrite the file in place

Exit codes

Code Meaning
0 Success
1 Runtime error or missing file
2 Parse error

Script path resolution

When you write verbai run examples/01_hello.verbai, the CLI looks first in the current working directory. If the file is not there, it falls back to the bundled copy inside the installed verbai package — so PyPI users don't need to clone the private GitHub repo to follow along with this README.


Project layout

verbai/
├── src/verbai/              # the package
│   ├── __init__.py
│   ├── __main__.py          # `python -m verbai` entry point
│   ├── cli.py               # argparse + subcommand routing
│   ├── lexer.py             # tokens for the verbai source language
│   ├── parser.py            # tokens → AST
│   ├── ast_nodes.py         # dataclass AST node types
│   ├── interpreter.py       # tree-walking evaluator
│   ├── runtime.py           # Value types, RuntimeContext, run_source()
│   ├── symbol_table.py      # scope chain + name resolution
│   ├── stdlib.py            # built-in verbs (Print, Run, …)
│   ├── format.py            # canonical formatter
│   ├── providers/
│   │   ├── __init__.py      # Provider ABC + registry
│   │   ├── base.py          # base re-export
│   │   ├── mock.py          # MockProvider (default, no key)
│   │   └── minimax.py       # MiniMaxProvider (real API)
│   └── examples/            # shipped inside the wheel
│       ├── 01_hello.verbai
│       ├── 02_constants.verbai
│       └── … (14 total)
├── tests/                   # pytest suite (169 passing)
├── docs/
│   ├── specs/               # language design spec
│   └── cookbook.md          # chapter 1 prose walk-through
├── pyproject.toml           # build + deps + pytest config
├── LICENSE                  # Apache 2.0
├── README.md                # this file
├── PUBLISHING.md            # maintainer-only PyPI release guide
└── AGENTS.md                # notes for AI coding agents

Running the tests

pip install -e ".[dev]"           # installs pytest, pytest-cov, build, twine
python -m pytest                  # 169 passing, 2 skipped (real-API gated)
python -m pytest --cov=verbai --cov-fail-under=85   # coverage gate (≥ 85 %)
python -m pytest -m real_api      # opt-in: runs example 14 against real MiniMax

Use python -m pytest rather than bare pytest so the right Python interpreter wins (your gitbash PATH may point at a different Python than your python does).


Glossary

Term Meaning
AST Abstract Syntax Tree. The structured representation of your script that the interpreter walks.
Capability check A check (planned for v0.2) that a provider supports the model + output type your script asks for. v0.1's substitute is that Run simply raises ProviderError if the provider doesn't know what to do.
Closure The captured lexical scope that travels with a FunctionValue. Lets lambdas see the bindings that were in scope where they were defined.
do block do … end — a block of statements and <- bindings. Each binding is immutable within the block; child scopes can shadow outer names.
Fallback The Fallback verb: tries each expression in turn, returning the first that doesn't raise ProviderError.
Field access obj.field — looks up a key in an ObjectValue. Returns Nil if the key is missing.
ForEach The verb that iterates over a list, optionally in parallel via ForEach.Parallel.
Function declaration let name p1 p2 = body — desugars to let name = \p1 p2 -> body.
Hybrid syntax A single script can mix declarative let/do blocks with procedural expression statements.
Immutable binding A let NAME = … or <- NAME binding cannot be reassigned. Re-declaring in the same scope raises NameError; shadow with a nested scope.
Keyword-block call f { a = 1 ; b = 2 } — sugar for f(a=1, b=2). Used by Run. Object literals ({ a = 1 }) without a leading function are wrapped in parens to force positional treatment.
Lambda Anonymous function. \(x -> body) in source, written as (x -> body) in the language.
Mock provider The built-in mock provider used in tests and offline examples. No API key, returns canned responses.
Module scope The outermost lexical scope of a .verbai script. let bindings at the top level land here.
Multi-modal Inputs/outputs beyond text: image, audio, video. Planned for v0.2.
MCP Model Context Protocol. A protocol for connecting verbai to tool-using agents. v0.2.
Pattern (in catch) Either _ (always catch) or a name like err (bind the error to that name and run the catch). Some error-kind names also match by kind.
Pipe `a
Provider A pluggable Python class implementing verbai.providers.Provider. Each one wraps an external model API (or mock).
ProviderError A VerbError with kind=kValue("ProviderError"). Raised when a provider call fails. Fallback catches only this kind.
PyPI Python Package Index — the public package registry at pypi.org. pip install verbai pulls from here.
REPL Read-Eval-Print Loop. v0.2.
Retry The Retry verb: re-runs an expression up to n times on failure, with optional exponential backoff.
Run The verb that dispatches to a provider's run_inference(model, input, **kwargs).
Stdlib The standard library of built-in verbs in src/verbai/stdlib.py.
Test PyPI test.pypi.org — a sandbox PyPI instance. We do not use this in v0.1; the pipeline goes straight to real PyPI.
Try/catch try EXPR catch err -> EXPR end — runs the body, recovers if it raises VerbError.
Value A runtime value: IntValue, FloatValue, TextValue, BoolValue, NilValue, ListValue, ObjectValue, FunctionValue, or ProviderHandleValue.
Verb A callable exposed in the global scope. Built-in verbs (Print, Run, …) live in stdlib.py; user-defined functions become verbs when bound at module scope.
Wheel A built distribution (.whl) that pip install consumes. We build both a wheel and an sdist; the wheel contains the example programs as package data.

What's next

  • v0.2: examples 15–31 from the spec curriculum (multi-modal, MCP, HTTP, webhooks, more providers).
  • v0.2: formal type checker (capability checks stay as the v0.1 substitute).
  • v0.3: LSP server, syntax highlighting, REPL.

License

Apache 2.0. See LICENSE.

Metadata

Release files for verbai 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for verbai 0.1.1
File Size Uploaded Provenance Actions
verbai-0.1.1.tar.gz 66.5 kB Without provenance Details for verbai-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for verbai 0.1.1
File Interpreter ABI Platform Actions
verbai-0.1.1-py3-none-any.whl Python 3 none any Details for verbai-0.1.1-py3-none-any.whl

Total release size: 123.0 kB

Release files / verbai-0.1.1.tar.gz

Download URL verbai-0.1.1.tar.gz
Size 66.5 kB
Tags Source
SHA-256 checksum
How to use checksums
9aee130eac3250839370fd8ac46793eeda1b542037960900701084f5e5c10989
BLAKE2b-256 checksum
How to use checksums
6c7c2158d8cda16ec706922b8cc695d8448b2612659788635ec2bde9ddbf7d52
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / verbai-0.1.1-py3-none-any.whl

Download URL verbai-0.1.1-py3-none-any.whl
Size 56.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
10ae54026700194b9e4e5c35714d965509c94b4d5b4198c92c156da26f8fcd13
BLAKE2b-256 checksum
How to use checksums
078f9abd8501fa762a192ec6255e2b4872460f7735ca4a81c1fc036e2ccd2b2b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Security for verbai 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page