OCaml-first SDK for LLM providers with an Eio native runtime and a zero-runtime-dependency JavaScript package generated by Melange. Chatoyant provides typed provider clients, structured outputs, tool calling, streaming, token/cost accounting, and root-only npm exports.
chatoyant /shuh-TOY-uhnt/ - having a changeable lustre.
- Native OCaml API built around Eio, result-returning calls,
.mlicontracts, and typed tool definitions. - Melange-generated npm package with one root import path, ESM and CommonJS
entrypoints, colocated
.d.tsand.d.ctsdeclarations, and no runtime npm dependencies. - Unified
Chatsession API, one-shot text/data/stream shortcuts, tool calling, streaming accumulation, JSON roundtrip, and token/cost accounting. - Raw provider clients for OpenAI, Anthropic, xAI, Meta (Muse Spark), OpenRouter, and local OpenAI-compatible servers.
- Standalone Draft 2020-12 JSON Schema parser/validator with OpenAI strict projection and typed OCaml codec generation.
- Production-derived JS usage tests, OCaml native tests, TypeScript declaration checks, package metadata checks, and the official JSON Schema suite against the bundled npm package.
| Provider | Env var | Detection |
|---|---|---|
| OpenAI | OPENAI_API_KEY |
gpt-*, o1-*, o3-*, o4-*, chatgpt-* |
| Anthropic | ANTHROPIC_API_KEY |
claude-* |
| xAI | XAI_API_KEY |
grok-* |
| Meta | META_API_KEY (or MODEL_API_KEY) |
muse-* |
| OpenRouter | OPENROUTER_API_KEY |
Slash notation such as openai/gpt-4o |
| Local | LOCAL_BASE_URL |
Explicit provider: "local" or local fallback |
Model presets are available for quick intent-based calls: fast, cheap,
balanced, best, and reasoning.
open Chatoyant
let () =
Eio_main.run @@ fun env ->
let ai = Chatoyant.openai ~model:"gpt-5.6-luna" env in
match Chatoyant.gen_text ai "Say hello in three words." with
| Ok text -> print_endline text
| Error err -> prerr_endline (Chatoyant.Error.provider err)Typed tools are ordinary modules. Comments become schema descriptions, option
means optional, and the generated tool value plugs into a chat.
module%tool Calculate = struct
type operation =
| Add
| Divide
type request = {
operation : operation; (** Operation to apply. *)
values : float list [@min_items 1]; (** Numbers to combine in order. *)
}
type answer = { result : float }
(** Combine numbers with a typed arithmetic operation. *)
let run : request -> (answer, string) result =
fun { operation; values } ->
match operation, values with
| _, [] -> Error "at least one value is required"
| Add, values -> Ok { result = List.fold_left ( +. ) 0. values }
| Divide, first :: rest ->
List.fold_left
(fun acc value -> Result.bind acc (fun n ->
if value = 0. then Error "division by zero" else Ok (n /. value)))
(Ok first) rest
|> Result.map (fun result -> { result })
end
let () =
Eio_main.run @@ fun env ->
let ai =
Chatoyant.openai ~model:"gpt-5.6-luna" ~tools:[ Calculate.tool ] env
in
ignore (Chatoyant.gen_text ai "Divide 83 by 3.")For the full native guide, see OCAML.md.
import { Chat, Schema, createTool, genData, genStream, genText } from "chatoyant";
const text = await genText("What is 2+2?", {
model: "fast",
system: "Return short answers.",
});
class Person extends Schema {
name = Schema.String({ description: "Person name" });
age = Schema.Integer({ minimum: 0 });
}
const person = await genData("Extract: Alice is 30 years old", Person);
for await (const chunk of genStream("Write a haiku about typed APIs.")) {
process.stdout.write(chunk);
}
const lookup = createTool({
name: "lookup",
description: "Lookup data",
parameters: { q: Schema.String({ minLength: 1 }) },
async execute({ args }) {
return { found: args.q };
},
});
const chat = new Chat({ model: "gpt-4o" });
chat.system("Use tools when useful.").user("Find needle").addTool(lookup);
console.log(await chat.generate());All JavaScript imports come from the package root:
import { Chat, OpenAI, OpenRouter, Tokens, JsonSchema, genText } from "chatoyant";Former subpath imports from the TypeScript package intentionally move to this root surface. See JAVASCRIPT.md.
From the repository root:
make build # OCaml + Melange build, then the esbuild npm bundle
make test # native tests, JS parity tests, tsc, and the JSON Schema suite
make check # the full release gate
make help # list all targetsmake check builds and tests the native OCaml package first, emits Melange ESM,
bundles the npm package with esbuild, runs Node's native tests against
dist/index.js, checks dist/index.d.ts with tsc, runs the pinned official
JSON Schema suite through the bundled package, lints opam metadata, checks local
documentation links, verifies package metadata, and dry-packs the npm artifact.
See CONTRIBUTING.md for setup, the project layout, and the release process, and CHANGELOG.md for release history.
If this package helps your project, consider sponsoring its maintenance: GitHub Sponsors.
