package ocaml-ai-sdk
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
On This Page
OCaml AI SDK - Provider abstraction for AI models
Install
dune-project
Dependency
Authors
Maintainers
Sources
ocaml-ai-sdk-0.6.3.tbz
sha256=0cc0a957784c295877b6bcc4f80c0dba0f6507d3c282e7d6cbd71038050376de
sha512=f8e082376b4de194a877d0df193892cdaa8139a4789f51eebac651acf8b6d4064b95242e158d86c1a32776d99130d43ed4c1119e2d54e738e93bdba2a6ad9a33
doc/CHANGELOG.html
Changelog
All notable changes to this project will be documented in this file.
Unreleased
0.6.3 — 2026-09-08
Anthropic provider (ai_provider_anthropic)
- The
Invalid_argumentraised for an unsupported thinking or effort setting now says what to use instead. PassingThinking.Enabledto an adaptive-generation model (Fable 5, Mythos 5, Opus 5, Opus 4.8, Sonnet 5) namesThinking.AdaptiveplusAnthropic_options.effortas the successor to manual token budgets, and explains that the caller picks the effort level because no faithful budget-to-effort mapping exists. The adaptive, disabled-thinking, and forced-tool-choice rejections got the same treatment. Which inputs are rejected has not changed. - The effort rejection now names the levels the model does accept, falling back to "accepts no effort levels" only when the list is genuinely empty. Every catalog model with effort support currently accepts all five levels or none, so today this reads the same; it stays correct if a partial-effort model is added.
Effort.equalis now exposed, replacing a polymorphic comparison in the effort check.Anthropic_model.unsupported_effort_messageis exposed so a test can pin the partial-effort wording. No catalog model has a partialeffort_levelslist, so that branch cannot be reached throughcreate; the test drives the builder directly and cross-checks the empty-list case against the messagecreateactually raises.Thinking.tin the mli now documents which models accept each constructor, thatAdaptivewith an effort level replacesEnabled's budgets, thatDisabledis not universally supported, and that omitting thinking is not the same as disabling it (Opus 4.8 and Haiku 4.5 do not think by default, the other adaptive-generation models do). It also records that a custom model id has unknown capabilities, so manual budgets pass through to the API untouched instead of being rejected.
0.6.2 — 2026-08-19
OpenRouter provider (ai_provider_openrouter)
- The
annotationsfield on chat messages and stream deltas now decodes an explicit JSONnull. Some OpenAI-compatible servers (vLLM) serialize an absentannotationsasnullon every response, which previously failed with "expected array but got null". This matches OpenAI's own client, which types the field as optional. A malformed non-null value is still rejected, and the remaining array fields keep the strict spec shape.
0.6.1 — 2026-08-13
Breaking changes
Ai_provider_anthropic.Model_catalog.known_modeldroppedClaude_opus_4_7,Claude_opus_4_6, andClaude_sonnet_4_6. These model ids still work through theCustompass-through, but they no longer resolve to a known constructor, so they lose catalog-backed capabilities (Customreports no thinking support, no structured output, and no prompt caching). Exhaustive matches overknown_modelmust drop the removed arms.
Anthropic provider (ai_provider_anthropic)
- Restored
Claude_haiku_4_5, dropped from the catalog in 0.6 by oversight.to_model_idreturns the pinnedclaude-haiku-4-5-20251001, andof_model_idaccepts bothclaude-haiku-4-5and the dated id. Capabilities followdocs/plans/2026-08-03-adaptive-thinking-support.md: manual budgeted thinking only (no adaptive), no effort levels,Summarizeddisplay default, explicit disable allowed, 64k max output tokens, and a 4096-token cache minimum. It accepts sampling parameters, unlike the 5-series models.
0.6 — 2026-08-12
Breaking changes
Ai_provider.Provider_error.tgainedretry_after_s. Custom providers and direct record constructors must set it toNonewhen no server hint exists.Ai_core.Generate_text_result.stepgainedresponse_model. Direct record constructors must set it toNonefor steps without a provider response.Ai_provider.Stream_part.Reasoninggainedprovider_options. Custom providers must set it toAi_provider.Provider_options.emptywhen they have no reasoning metadata.Ai_core.Text_stream_part.Reasoning_start/Reasoning_delta/Reasoning_endand the matchingUi_message_chunkconstructors gainedprovider_metadata. Custom transforms and chunk producers must set it toNonewhen no metadata is available.Ai_provider_anthropic.Convert_response.content_block_jsongaineddata. Direct record constructors must set it toNonefor non-redacted blocks.Ai_provider_anthropic.Convert_prompt.anthropic_contentgainedA_redacted_thinking; exhaustive pattern matches must handle it.Ai_provider_anthropic.Thinking.tis nowEnabled,Adaptive, orDisabled; migrate{ enabled = true; budget_tokens }toEnabled { budget_tokens; display = None }and{ enabled = false; _ }toDisabled.Ai_provider_anthropic.Anthropic_options.tgainedeffort, andAnthropic_api.output_configgainedeffortwhile makingformatoptional. UseNonefor either field when it is not configured.Ai_provider_anthropic.Beta_headers.required_betasnow acceptsthinking:Thinking.t option; replace the oldtruewithSome (Enabled ...)andfalsewithNone.Ai_provider_anthropic.Model_catalog.model_capabilitiesreplacedsupports_thinkingwith detailedthinkingcapabilities and addedrejects_sampling_parameters.
Core SDK (ai_core)
- Retryable provider errors now honor a server
retry_after_shint with upstream replace semantics: an accepted hint replaces the exponential backoff for that attempt (it can shorten as well as lengthen the delay, rather than acting as a lower bound). Following upstream, a hint is accepted only when reasonable — non-negative and either below 60s or below the un-jittered exponential delay for the attempt; otherwise it is rejected and the jittered exponential backoff is used. Jitter never applies to an accepted hint.generate_text,stream_text, andServer_handler.handle_chataccept?max_retry_delay_ms: an ordinary backoff delay above the cap is clamped down to it, while an accepted hint above the cap stops retries without sleeping or issuing another request. - Generation steps expose the provider-reported
response_model, including distinct models selected on successive tool-loop calls and OpenRouter streams.
Provider errors (ai_provider)
- Added
Ai_provider.Retry_after.parse, a shared parser turningretry-after-ms(milliseconds, more precise, used by e.g. OpenAI) andretry-after(fractional or integer seconds) headers intoretry_after_sseconds.retry-after-mstakes precedence, except a non-numeric or NaNretry-after-msfalls through toretry-after(matching upstreamparseFloat). Infinite and negative values yield no hint. The HTTP-date form ofretry-afteris not supported.
Provider wiring (ai_provider_anthropic, ai_provider_openai, ai_provider_openrouter)
- Anthropic, OpenAI, and OpenRouter HTTP error responses now populate
retry_after_sfrom bothretry-after-msandretry-afterresponse headers via the shared parser (fractional seconds and the millisecond header are both honored; previously OpenRouter parsed only integerretry-afterseconds).Anthropic_error.of_responseandOpenai_error.of_responsegained optional?retry_after_ms/?retry_afterheader arguments and a trailingunit;Openrouter_error.of_response_with_retry_aftergained an optional?retry_after_ms. The custom JSON-onlyfetchcallbacks carry no headers and therefore no retry hint. - OpenRouter streaming responses preserve the reported model and serving provider.
0.5 — 2026-07-06
OpenRouter provider (ai_provider_openrouter)
- Error handling hardened. Completions with
finish_reason = "error"now map toFinish_reason.Error, and error-shaped completion bodies (top-level or per-choiceerrorobjects) raise aProvider_errorinstead of being reduced to empty output. The error body is a human-readable message (upstream provider name, the most specific upstream message fromerror.metadata.raw, and theerror_typesuffix) rather than a raw JSON dump. Retryability now keys off the real HTTP status on the transport error path (an inner providererror.codeno longer overrides a 5xx gateway status), and 200-embedded / streaming errors derive their status fromerror.code, tolerating string and float encodings.
0.4 — 2026-06-02
Breaking changes
Source-breaking for downstream code that constructs these records directly or pattern-matches without ; _. Code that goes through the documented constructors (Core_tool.create, Prompt_builder.resolve_messages, Cache_control.ephemeral / ephemeral_1h) is unaffected.
Ai_provider.Prompt.Systemgainedprovider_options : Provider_options.talongsidecontent. Callers buildingSystem { content }literally must addprovider_options = Ai_provider.Provider_options.empty.Ai_provider.Tool.tgainedprovider_options : Provider_options.t. The in-tree OpenAI / OpenRouter providers were updated; external providers constructing this record need the same.Ai_provider.Stream_part.Finishgainedprovider_metadata. The standaloneProvider_metadataconstructor is removed — its data now rides onFinish.Ai_core.Core_tool.tgainedprovider_options. Thecreate/create_with_approval/create_client_toolhelpers take an optional?provider_optionsdefaulting to empty, so call sites that use them are unaffected.Ai_provider_anthropic.Cache_control.tgainedttl : ttl option. Construct viaCache_control.ephemeral(5m, default) orCache_control.ephemeral_1h.- Removed
Ai_provider_anthropic.Cache_control.breakpoint_to_json/breakpoint_of_jsonfrom the public mli — they silently dropped thettlfield and had no in-tree callers.
Anthropic provider (ai_provider_anthropic)
- Prompt caching reaches the high-level API. The
cache_controlplumbing landed in 0.3 was only usable throughAi_provider.Language_modeldirectly.Ai_core.Generate_text.generate_text,Ai_core.Stream_text.stream_text, andAi_core.Server_handler.handle_chatnow accept?system_provider_optionsfor the prepended system prompt, andCore_tool.tgains aprovider_optionsfield that flows through to the providerToolrecord. The runnableexamples/prompt_cachingdemo exercises the new path. Stream_part.Finishcarriesprovider_metadatamatching upstreamLanguageModelV4StreamPart. The standaloneProvider_metadatachunk is removed; Anthropic cache token metrics now ride on the terminalFinishchunk on cached requests, andStream_text_result.provider_metadata/Generate_text_result.step.provider_metadataexpose them to callers. Cache fields are read frommessage_start.message.usage(where Anthropic actually emits them) with a fallback tomessage_delta.usage.- System messages always serialize as array-of-blocks on the wire, matching upstream
@ai-sdk/anthropic. The previous "joined string when nocache_control, array whencache_controlis set" behavior changed the model's input based on a feature flag. - Anthropic usage decoders now ignore unknown fields, including nested
cache_creationfields, so new provider-side usage metadata does not break response parsing.
OpenRouter provider (ai_provider_openrouter)
- Prompt caching support. Added typed
Cache_control/Cache_control_optionsmodules for explicit per-block cache breakpoints, including Anthropic-compatiblettlvalues (5m/1h) and fallback support for prompts that already use the Anthropic cache-control key. - OpenRouter prompt conversion now mirrors upstream. The provider no longer reuses the OpenAI prompt converter for chat messages. It now emits OpenRouter-specific
cache_controlplacement for system, user, assistant, and tool messages, while preserving the no-cache shape for non-system messages. - Cache usage metadata. OpenRouter
usage.prompt_tokens_detailsis mapped into provider metadata ascache_read_tokensandcache_write_tokens. Addedexamples/openrouter_prompt_cachingto demonstrate both top-level automatic caching and explicit breakpoint mode.
0.3 — 2026-04-20
Anthropic provider (ai_provider_anthropic)
- Native Structured Outputs —
Object_jsonmode now uses Anthropic's nativeoutput_config.format = { type: "json_schema", schema }field on capable models (Haiku 4.5, Sonnet 4.5/4.6, Opus 4.5/4.6/4.7), matching upstream@ai-sdk/anthropic. Schema enforcement is handled by the provider, not by appending instructions to the system prompt. - Tool-use fallback — on older models (Sonnet 4.0, Opus 4.0/4.1) and unknown
Custommodel ids, the provider synthesises a tool namedjsoncarrying the schema asinput_schemaand forcestool_choice = { type: "tool", name: "json" }. The caller's system prompt is left untouched. - Prompt injection removed — the previous best-effort "Respond ONLY with JSON matching this schema…" system-prompt append has been deleted. Callers using
Object_json None(no schema) now receive anUnsupported_featurewarning because Anthropic cannot enforce JSON without a schema. - Model catalog — added
Claude_opus_4_7. Thesupports_structured_outputcapability flag is now accurate per model (previously defaulted totruefor all known models).
Core SDK (ai_core)
Output.parse_output— when a step has no assistant text, falls back to decoding thejsontool call'sargs. Enables end-to-end structured output on the Anthropic fallback path and on any future provider that adopts the same convention.Stream_text—Tool_call_deltaevents for thejsontool drive the partial-output parser, so streaming callers see incremental JSON on the fallback path with the same UX as the native path.
Provider abstraction (ai_provider)
- HTTP timeouts. New
Ai_provider.Http_timeoutsmodule andAi_provider.Http_clientwrapper. Defaults: 600s for response headers (request_timeout) and 300s for silence between streaming chunks (stream_idle_timeout). Override per-provider viaConfig.create ?timeouts. Conservative values chosen to catch stuck connections and bugs, not bound legitimate workloads — a 20-minute streaming response completes fine as long as chunks keep flowing. - New
Provider_error.Timeoutkind withphase(Request_headers|Stream_idle),elapsed_s, andlimit_s.is_retryableis derived:Stream_idleis retryable (connection is dead);Request_headersis not (server may already be processing the request). - Fix:
Sse.parse_eventsno longer hangs consumers on upstream errors. Previously, an exception from the upstream line stream left the output stream pending forever. It now closes cleanly (viapush None) and re-raises toLwt.async_exception_hookso the underlying bug stays visible. Mode.fallback_json_tool_name— exported constant ("json") naming the synthetic tool used by the structured-output tool-use fallback convention. Shared betweenai_coreand providers so the convention has a single source of truth.
Providers (ai_provider_openai, ai_provider_anthropic, ai_provider_openrouter)
- Each
Config.tgains atimeouts : Http_timeouts.tfield. All HTTP traffic now routes throughHttp_client, removing three copies of the unguardedbody_to_line_streamhelper.
Examples
structured_output— live-API smoke test exercising both the native and tool-fallback paths withppx_deriving_jsonschemafor schema derivation andmelange-json-native'sof_jsonderiver for typed response decoding.
0.2 — 2026-04-14
Core SDK (ai_core)
Smooth_stream— stream transformer that buffersText_deltaandReasoning_deltachunks and re-emits them in controlled pieces with configurable inter-chunk delays. Five chunking modes:Word(default),Line,Regex(custom Re2 pattern),Segmenter(Unicode UAX#29 word boundaries via uuseg, recommended for CJK), andCustom(user function). Matches the upstream AI SDK'ssmoothStreamtransform.?transformparameter onstream_textandserver_handler.handle_chat— generic stream transformer (Text_stream_part.t Lwt_stream.t -> Text_stream_part.t Lwt_stream.t) applied between the raw event stream and consumer-facing streams. Bothfull_streamandtext_streamreflect the transformed output.- Retry with exponential backoff —
Retrymodule with jitter, configurable initial delay and backoff factor, and parameter validation.?max_retriesthreaded throughgenerate_text,stream_text, andserver_handler.handle_chat. Retries only on errors marked retryable. - Telemetry / observability —
Telemetrymodule with OpenTelemetry-compatible span instrumentation via thetracelibrary (ocaml-trace). ConfigurableTelemetry.tsettings control enable/disable, input/output recording privacy, function ID, custom metadata, and lifecycle integration callbacks (on_start,on_step_finish,on_tool_call_start,on_tool_call_finish,on_finish). Span hierarchy matches upstream AI SDK:ai.generateText/ai.streamTextroot spans,*.doGenerate/*.doStreamstep spans, andai.toolCalltool execution spans.?telemetryparameter threaded throughgenerate_text,stream_text, andserver_handler.handle_chat.
Provider Abstraction Layer (ai_provider)
is_retryablefield onProvider_error.t— defaults from HTTP status code (429, 5xx are retryable). Anthropic and OpenAI providers set it explicitly based on error classification.
Examples
smooth_streaming— demonstrates all five chunking modestelemetry_logging— demonstrates integration callbacks for lifecycle logging
Dependencies
- Added
re2(>= 0.16) anduuseg(>= 17.0) toai_core - Added
trace(>= 0.12) toai_core
0.1 — 2026-04-06
Initial release of the OCaml AI SDK — a type-safe, provider-agnostic AI model abstraction inspired by the Vercel AI SDK, targeting AI SDK v6 wire compatibility.
Provider Abstraction Layer (ai_provider)
- Extensible GADT-based
Provider_optionsfor compile-time type-safe provider-specific settings - Role-constrained
Prompttypes (System = string only, User = text + files, etc.) Language_model.Smodule type with first-class module wrapperTool,Tool_choice,Mode,Contentfoundation typesFinish_reason,Usage,Warning,Provider_errortypesProvider.SandMiddleware.Smodule type signaturesCall_options,Generate_result,Stream_part,Stream_resulttypes
Anthropic Provider (ai_provider_anthropic)
- Full Anthropic Messages API implementation with streaming (SSE)
Thinkingsupport withbudget_tokenssmart constructor (>= 1024)Cache_controlfor prompt cachingAnthropic_optionsvia the extensible GADT system- Model catalog with all Claude models (Opus, Sonnet, Haiku families)
- Beta header management and model-aware
max_tokens - Prompt conversion with message grouping, tool conversion, response parsing
- Provider factory and public API
OpenAI Provider (ai_provider_openai)
- OpenAI Chat Completions API implementation with streaming (SSE)
- Model catalog with GPT-4o, GPT-4o-mini, o1, o3, o4-mini families
- Tool calling with strict mode support
- Prompt conversion, response parsing, and provider factory
Core SDK (ai_core)
generate_text— synchronous text generation with multi-step tool loopstream_text— streaming text generation with multi-step tool loop, returns synchronously with streams filled by background Lwt task- Output API —
Output.text,Output.object_,Output.enum,Output.array,Output.choicewith JSON Schema validation - UIMessage stream protocol — SSE
data: {json}\n\nencoding withx-vercel-ai-ui-message-stream: v1header, all v6 chunk types Ui_message_stream_writer— composable stream builder withwrite(synchronous) andmerge(non-blocking viaLwt.async), lifecycle management, ref-counted in-flight merge tracking,on_finishcallback- Server handler — cohttp endpoint for chat with CORS support, v6-only request parsing with full part type support (text, file, reasoning, tool invocations with all states)
- Tool approval workflow —
needs_approvalpredicate onCore_tool.t, step loop partitioning,Tool_approval_requestchunk type, stateless re-submission withapproved_tool_call_ids Stop_condition— step loop termination predicates matching upstreamstopWhen:step_count_is,has_tool_call,is_met(OR semantics with short-circuit); wired throughgenerate_text,stream_text, andserver_handler;max_stepsremains as independent hard safety cap- Partial JSON parser — for streaming structured output
Melange Bindings (ai-sdk-react)
useChatanduseCompletionhook bindings for@ai-sdk/react- All v6 message part types including
data_ui_part classifyfunction for part type dispatch- Module-scoped accessors for ergonomic use from OCaml/Reason
Examples
one_shot,streaming,tool_use,thinking,generate,stream_chat,agent_loop— standalone CLI exampleschat_server— cohttp chat server with React frontend, tool approval, structured outputcustom_stream— custom data streaming with Melange frontendai-e2e— end-to-end Melange app with 11 demos (basic chat, reasoning, tool use, tool approval, client tools, file attachments, structured output, completion, web search, retry/regenerate)
Infrastructure
- Dune build with
generate_opam_filesfor automated opam file generation - mlx dialect support (OCaml + JSX via
mlx-pp/ocamlformat-mlx) - Alcotest test suites for all three libraries
- SSE wire format snapshot tests
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
On This Page