Library Guide
Caravan is three findlib libraries: Caravan (core), CaravanProviders,
CaravanTools. Full signatures live in the
API reference.
A typed pipeline in ten lines
open Caravan
open Caravan.Chain
let fact_chain net provider =
prompt_template "List 3 facts about {{topic}}."
|>> llm net provider
|>> parse Parser.numbered_list
let () = Eio_main.run (fun env ->
let provider = CaravanProviders.Ollama.make_provider ~model:"llama3.2" () in
match run (fact_chain env#net provider) [("topic", "OCaml")] with
| Ok facts -> List.iter print_endline facts
| Error e -> prerr_endline e)
|>> is Result-bind: every stage is 'a -> ('b, string) result.
Also available: parallel, retry ~n, Kleisli >=>.
Sessions and agents
let sess = Session.create ~tools:CaravanTools.All_tools.all_tools model provider in
let sess = Session.set_system sess "Be terse." in
match Agent.run env#net env#clock sess "count the .ml files here" with
| Ok (_sess, result) -> print_endline result.value.content
| Error e -> prerr_endline e
Wrap calls in Effects.with_net env#net so network tools reuse your
event loop, and in
Effects.run_with_effects ~permission_policy:(Permission.policy_of_mode "ask")
to govern tools.
Writing a tool
A tool is a module satisfying Caravan.Tool.TOOL — typed input/output,
a JSON schema, and an execute. Drop the file in lib/tools/; the
build-time generator registers any file that defines
module <Capitalized-filename>:
(* lib/tools/word_count.ml *)
open Caravan.Tool
module Word_count = struct
let name = "word_count"
let aliases = ["wc"]
let description = "Counts words in a string."
type input = string
type output = int
let json_schema () = `Assoc [
"type", `String "object";
"properties", `Assoc ["text", `Assoc ["type", `String "string"]];
"required", `List [`String "text"] ]
let parse_args json =
try Ok Yojson.Safe.Util.(json |> member "text" |> to_string)
with _ -> Error "expected {\"text\": …}"
let format_output n = string_of_int n
type _ Effect.t += Exec : input -> output Effect.t
let execute text =
String.split_on_char ' ' text |> List.filter (( <> ) "") |> List.length
end
If the tool mutates state, add its name to Permission.mutating_tools.
Writing a provider
Any OpenAI-compatible endpoint needs no code — add a registry entry
(one record in lib/providers/registry.ml) or just use base_url.
For a genuinely different API, implement Caravan.Provider.PROVIDER
(four functions: name, complete, stream, list_models) and pack it
with Provider ((module M), cfg).
Observing everything: Trace
Trace.add_sink (function
| Trace.Tool_call_end { name; duration; _ } ->
Printf.eprintf "%s took %.1fs\n" name duration
| _ -> ());
(* or capture scoped: *)
let (result, events) =
let acc = ref [] in
let r = Trace.with_sink (fun e -> acc := e :: !acc) run_my_agent in
(r, List.rev !acc)
Trace.open_transcript ~dir attaches the JSONL sink the CLI uses.
Memory back-ends
Memory.Ring (sliding window), Memory.Summary (compact-on-demand),
Memory.Hierarchical (rolling summaries), Redis_store (shared,
multi-process). Sessions accept any MEMORY implementation via the
packed existential.