Thin OpenAI API client for MoonBit built on gaato/http and gaato/mttp: models, embeddings, responses (buffered and streaming).
Status: unofficial and experimental. Minor releases may contain breaking changes; the changelog lists each one.
moon add gaato/openai
moon add gaato/http
moon add gaato/mttp
moon add gaato/http-async///|
let backend = @codings.Decompress::new(
@backend.AsyncBackend::new(config={
..@h1client.ClientConfig::default(),
connect_timeout_ms: Some(5000),
}),
)
///|
let clock = @runtime.AsyncClock::new()
///|
let timer = @runtime.ClockTimer::new()///|
/// Asks `model` one question and returns its answer.
pub async fn ask(
backend : &@http.Backend,
clock : &@rt.Clock,
timer : &@rt.Timer,
api_key~ : String,
model~ : String,
) -> Result[String, @openai.OpenAIFailure] {
let openai = @openai.OpenAI::new(api_key~, backend, clock, timer~)
let reply = openai.chat_completion(
@openai.ChatRequest::new(model~, messages=[
@openai.ChatMessage::user("Hello"),
]),
)
match reply {
Ok(reply) => Ok(reply.text)
Err(error) => Err(error)
}
}| Package | What it is |
|---|---|
| gaato/openai | The OpenAI client and its request, response and event types |
| gaato/openai/gen | Types and operations generated from the vendored OpenAPI spec |
///|
/// Streams a response, passing each piece of text to `on_text` as it arrives.
pub async fn stream_text(
openai : @openai.OpenAI,
model~ : String,
on_text : (String) -> Unit,
) -> Result[Unit, @openai.OpenAIFailure] {
openai.stream_response(
@openai.ResponseRequest::new(model~, input="Write a haiku about rivers."),
event => if event is OutputTextDelta(text) { on_text(text) },
)
}test {
let error : @openai.ApiErrorBody = {
message: "invalid request",
type_: Some("invalid_request_error"),
param: None,
code: None,
}
assert_eq(error.message, "invalid request")
}pub(all) enum ChatEvent {
Delta(String)
ReasoningDelta(String)
ToolCallDelta(index~ : Int, id~ : String?, name~ : String?, arguments~ : String)
Finished(FinishReason)
Usage(Usage)
Other(String, Json)
} derive(Eq, Debug)test {
let message = @openai.ChatMessage::user("hello")
assert_eq(message.role, "user")
assert_eq(message.content, "hello")
}test {
assert_eq(@openai.ChatMessage::assistant("hello").role, "assistant")
}fn ChatMessage::assistant_tool_calls(tool_calls : Array[ToolCall], content? : String) -> ChatMessagetest {
let message = @openai.ChatMessage::assistant_tool_calls([
{ id: "call_1", name: "lookup", arguments: "{}", },
])
assert_eq(message.tool_calls.unwrap().length(), 1)
}test {
assert_eq(@openai.ChatMessage::system("Be concise").role, "system")
}test {
let message = @openai.ChatMessage::tool(tool_call_id="call_1", content="done")
assert_eq(message.tool_call_id, Some("call_1"))
}test {
assert_eq(@openai.ChatMessage::user("hello").role, "user")
}fn ChatRequest::new(model~ : String, messages~ : Array[ChatMessage], max_tokens? : Int, temperature? : Double, reasoning_effort? : String, tools? : Array[ToolDef], tool_choice? : Json, extra? : Map[String, Json]) -> ChatRequesttest {
let request = @openai.ChatRequest::new(model="gpt-4o-mini", messages=[
@openai.ChatMessage::user("hello"),
])
assert_eq(request.messages.length(), 1)
}fn EmbeddingRequest::new(model~ : String, input~ : Array[String], dimensions? : Int, user? : String) -> EmbeddingRequesttest {
let request = @openai.EmbeddingRequest::new(model="text-embedding-3-small", input=[
"hello",
])
assert_eq(request.input, ["hello"])
}pub struct OpenAI {
// private fields
}async fn OpenAI::chat_completion(self : OpenAI, request : ChatRequest) -> Result[ChatCompletion, OpenAIFailure]async fn OpenAI::create_embeddings(self : OpenAI, request : EmbeddingRequest) -> Result[EmbeddingResponse, OpenAIFailure]async fn OpenAI::create_response(self : OpenAI, request : ResponseRequest) -> Result[Response, OpenAIFailure]async fn OpenAI::stream_chat_completion(self : OpenAI, request : ChatRequest, f : async (ChatEvent) -> Unit) -> Result[Unit, OpenAIFailure]async fn OpenAI::stream_response(self : OpenAI, request : ResponseRequest, f : async (ResponseEvent) -> Unit) -> Result[Unit, OpenAIFailure]pub(all) enum ResponseEvent {
Created(Response)
InProgress(Response)
OutputTextDelta(String)
OutputTextDone(String)
FunctionCallArgumentsDelta(item_id~ : String, delta~ : String)
FunctionCallArgumentsDone(item_id~ : String, arguments~ : String)
Completed(Response)
Failed(Response)
Incomplete(Response)
Error(ApiErrorBody)
Other(String, Json)
} derive(Eq, Debug)fn ResponseRequest::new(model~ : String, input~ : String, instructions? : String, max_output_tokens? : Int, temperature? : Double, previous_response_id? : String, tools? : Array[ToolDef], tool_choice? : Json, extra? : Map[String, Json]) -> ResponseRequesttest {
let request = @openai.ResponseRequest::new(model="gpt-4.1", input="hello")
assert_eq(request.extra, {})
}fn ResponseRequest::with_tool_outputs(self : ResponseRequest, outputs : Array[(String, String)]) -> ResponseRequesttest {
let request = @openai.ResponseRequest::new(model="gpt-4.1", input="continue").with_tool_outputs([
("call_1", "done"),
],
)
assert_eq(request.input, "continue")
}test {
let call : @openai.ToolCall = {
id: "call_1",
name: "lookup",
arguments: "{\"id\":1}",
}
assert_eq(call.arguments_json(), { "id": 1 })
}pub struct ToolCallAccumulator {
// private fields
}test {
let calls = @openai.ToolCallAccumulator::new()
calls.feed(index=0, id="call_1", name="lookup", arguments="{}")
assert_eq(calls.finish()[0].arguments, "{}")
}fn ToolCallAccumulator::feed(self : ToolCallAccumulator, index~ : Int, id? : String, name? : String, arguments~ : String) -> Unittest {
let tool = @openai.ToolDef::new(name="lookup", parameters={ "type": "object" })
assert_eq(tool.name, "lookup")
}Install
Download zipThin OpenAI API client for MoonBit built on gaato/http and gaato/mttp: models, embeddings, responses (buffered and streaming).