Metadata-Version: 2.4
Name: verbai
Version: 0.1.1
Summary: A Haskell-flavored DSL for AI model orchestration.
Author-email: Gerald Enrique Nelson Mc Kenzie <lordxmen2k@gmail.com>
License: Apache-2.0
Project-URL: Homepage, https://github.com/lordxmen2k/verbai
Project-URL: Repository, https://github.com/lordxmen2k/verbai
Keywords: dsl,ai,llm,orchestration,haskell
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Interpreters
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: ollama
Provides-Extra: openai
Provides-Extra: anthropic
Provides-Extra: cloud
Provides-Extra: local
Provides-Extra: all
Dynamic: license-file

# 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](#quickstart)
2. [Installation](#installation)
3. [How verbai works — the mental model](#how-verbai-works--the-mental-model)
4. [Provider setup guide](#provider-setup-guide)
5. [Built-in verbs (stdlib)](#built-in-verbs-stdlib)
6. [The example programs](#the-example-programs)
7. [CLI reference](#cli-reference)
8. [Project layout](#project-layout)
9. [Running the tests](#running-the-tests)
10. [Glossary](#glossary)
11. [What's next](#whats-next)
12. [License](#license)

---

## Quickstart

```bash
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

```bash
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

```bash
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.

```verbai
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`.

```python
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](https://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
# bash / gitbash
export MINIMAX_API_KEY='sk-cp-...'
verbai run examples/14_minimax_pong.verbai
unset MINIMAX_API_KEY
```

```powershell
# 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:

```verbai
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:

```python
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

```python
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:

```verbai
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`

```verbai
Print "hello, world"
```

```
$ verbai run examples/01_hello.verbai
hello, world
```

### 02 `02_constants.verbai`

```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`

```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`

```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`

```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`

```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`

```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`

```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`

```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`

```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`

```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`

```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`

```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)*

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

Run it:

```bash
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

```bash
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 |> b` — evaluates `a` then passes it as the first argument to `b`. Right-associative. |
| **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](https://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](https://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](LICENSE).
