Talk to language models from MoonBit: one contract for chat, streaming, tools and embeddings, with a subpackage per wire format.
let provider = @openai.Client::new(api_key="sk-...")
let reply = provider.chat(
@moonmuse.Request::new("gpt-4o-mini", [@moonmuse.Message::user("Why is the sky blue?")]),
)
println(reply.text())moon add moonbitstack/moonmuse| Contract | What it is | Who implements it |
|---|---|---|
| Provider | Ask for an answer, a stream, vectors, or the model list. | @openai, @anthropic, @gemini, @ollama, @fake, every layer, your own |
| Dialect | One vendor's wire format, read and written. | The four provider packages, your own |
| Package | What it adds |
|---|---|
| moonmuse | Messages, requests, replies, the two contracts, the server-sent-events reader |
| moonmuse/openai | OpenAI's format, plus every endpoint that speaks it — DeepSeek, Kimi, Qwen, vLLM, OpenRouter, DashScope, Mistral — and the Responses, moderation, image, file, batch and fine-tuning calls |
| moonmuse/anthropic | The Messages API, its event stream, and its token counter |
| moonmuse/gemini | generateContent, streamGenerateContent, embedContent |
| moonmuse/ollama | The native /api endpoints, including the newline-delimited stream |
| moonmuse/layers | Retry, fallback, rate limiting, caching, routing — each a provider around a provider |
| moonmuse/toolbox | Tool execution and the tool loop |
| moonmuse/models | Context windows, output limits, list prices, capabilities |
| moonmuse/structured | Ask for a JSON answer that fits a schema, with a repair loop |
| moonmuse/prompt | Prompt templates, rendered by moonpug |
| moonmuse/fake | A scripted provider, for tests of everything above the wire |
| moonmuse/serve | Export any provider as an OpenAI, Anthropic, Gemini or Ollama API |
| moonmuse/local | A local model as a provider: chat template, tool-call parsing, an engine contract |
let request = @moonmuse.Request::new(
"claude-sonnet-4-20250514",
[
@moonmuse.Message::system("Answer in one sentence."),
@moonmuse.Message::user("Why is the sky blue?"),
],
temperature=0.2,
max_tokens=256,
stop=["\n\n"],
)
let reply = provider.chat(request)
let (text, usage) = (reply.text(), reply.usage.total())let reply = provider.stream(request, delta => {
match delta {
Text(chunk) => print(chunk)
CallStart(_, name~) => println("\ncall " + name)
CallArgs(_, chunk~) => print(chunk)
Done(_) => println("")
}
})let tools = @toolbox.Toolbox::new()
tools.add(@moonmuse.Tool::new("add", "Add two integers"), args => {
(json_int(args, "a").unwrap_or(0) + json_int(args, "b").unwrap_or(0)).to_string()
})
let reply = @toolbox.run(provider, request, tools, limit=10)let gateway = @layers.cached(
@layers.retry(@layers.fallback([primary, secondary])),
@layers.Memory::new(limit=512),
)let answer = @structured.ask(
provider,
request,
name="weather",
schema=my_schema,
decode=json => json_int(json, "celsius"),
)let app = @serve.serve(
provider,
dialects=[@openai.dialect, @anthropic.dialect, @gemini.dialect, @ollama.dialect],
keys=@serve.StaticKeys::new(["sk-local"]),
)| Format | Endpoints |
|---|---|
| OpenAI | /v1/chat/completions, /v1/completions, /v1/responses, /v1/embeddings, /v1/models |
| Anthropic | /v1/messages, /v1/messages/count_tokens, /v1/models |
| Ollama | /api/chat, /api/generate, /api/embed, /api/tags, /api/show, /api/version |
| Gemini | /v1beta/models/*:generateContent, :streamGenerateContent, :embedContent |
let model = @local.Local::new(
engine, // your inference engine
template=@local.llama3(), // or @local.chatml()
calls=@local.Tagged, // how it writes a tool call
model="qwen2.5-7b",
)
let reply = model.chat(request) // a Provider like any otherlet info = @models.find("gpt-4o-mini")
let cost = @models.estimate_cost("gpt-4o-mini", reply.usage)pub(open) trait Dialect {
fn name(Self) -> String
fn request(Self, Request) -> Call raise LlmError
fn reply(Self, status : Int, body : Json) -> Reply raise LlmError
fn delta(Self, Frame, Turn) -> Array[Delta] raise LlmError
fn routes(Self) -> Array[Route]
fn parse(Self, Route, Json) -> Asked raise LlmError
fn answer(Self, Reply) -> Json
fn answer_embed(Self, String, Array[Array[Double]]) -> Json
fn answer_count(Self, Reply) -> Json
fn answer_models(Self, Array[ModelInfo]) -> Json
fn answer_at(Self, RouteKind, Reply) -> Json
fn frame(Self, Delta, Turn) -> Array[Frame]
fn error(Self, LlmError) -> (Int, Json)
}pub(all) struct ModelInfo {
id : String
context : Int?
output : Int?
input_price : Double?
output_price : Double?
capabilities : Array[Capability]
} derive(Eq, Debug)fn ModelInfo::new(id : String, context? : Int, output? : Int, input_price? : Double, output_price? : Double, capabilities? : Array[Capability]) -> ModelInfofn parse_int_lossy(text : String) -> IntInstall
Download zipTalk to language models from MoonBit: one contract for chat, streaming, tools and embeddings, with a subpackage per wire format.