{"slug": "language-integrated-llms-as-an-ocaml-function", "title": "Language integrated LLMs as an OCaml function", "summary": "Developer Anil Madhavapeddy released ocaml-deepseek, an OCaml library that integrates DeepSeek's open-weight LLM directly into applications via a native inference engine, enabling local, dependency-free AI agents. The package, submitted to the opam package manager, allows OCaml functions to be used as tools for the model, demonstrated with a web traffic analysis agent.", "body_md": "Fable [cut out on me](https://www.theverge.com/ai-artificial-intelligence/949553/anthropic-fable-5-mythos-5-government-national-security) at 1am on Saturday while I was sweeping over the OCaml runtime looking for concurrency bugs. There have been [excellent takes](https://x.com/rosstaylor90/status/2066067747738431504) on the sovereignity implications of this, and I figured I'd roll my sleeves up and get serious about using the open weights models. DeepSeek's models have been getting more capable since their [first release](/notes/deepseek-r1-advances), and [v4 Flash](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash) is small enough to run on my Mac (admittedly, very high-end Macs with 128GB/512GB of RAM respectively for my laptop and desktop).\n\nThe question is whether the agentic CLIs I've [been using](/notes/aoah-2025) can be easily replaced. The best way to learn how a system works is to build it [unikernel style](/projects/unikernels), and so I aimed to expose the LLM as a normal OCaml library.\nThis avoids routing via [bloated CLIs](https://github.com/anthropics/claude-code/issues/8382), and\nlets the linking application drive the agentic loop according to its specific needs.\n\nWhat makes this practical is [Antirez](https://github.com/antirez)'\n[Dwarfstar](https://github.com/antirez/ds4), a self-contained\nnative inference engine that supports [Apple Metal](https://developer.apple.com/metal/) and portable(ish) C.\nI bound this directly to OCaml 5 and [Eio](https://github.com/ocaml-multicore/eio) as\n** ocaml-deepseek**, and now a\nplain function call on my laptop gets me an LLM in my application.\n\nFor example, I can now embed Deepseek inference directly into the OCaml webserver that drives this very site in order to look for suspicious bot activity, and because it's open weights and running locally, there's no dependency on external services!\n\n```\n(* A traffic-triage agent in-process in OCaml. The agent is handed two\n   OCaml function tools and works out for itself how to combine them. *)\nlet agent =\n  Agent.create engine ~system:\"You are a web-traffic analyst.\"\n    ~tools:[\n      Toolbox.read ~dir:logs;   (* read-only sandboxed handle to the logs dir *)\n      query_db ~conn;           (* a SELECT-only tool over the local database *)\n    ]\nin\nAgent.send agent ~on_event\n  \"Cross-reference today's 404 spikes in the access log against the \\\n   client IPs in the requests table. Anything coordinated indicating a bad bot?\"\n```\n\nThe log reader and the database query are just two OCaml functions the model is allowed to call, each scoped and sandboxed (using Eio) to exactly what it needs. The model decides when and how to combine them.\n\n[1](#trying-out-humpty-the-ocaml-agent) Trying out Humpty the OCaml agent\n\nI've [submitted](https://github.com/ocaml/opam-repository/pull/30053) the package to opam, so `opam install deepseek`\n\nor `opam pin add deepseek https://tangled.org/anil.recoil.org/ocaml-deepseek.git`\n\nshould work.\nThe package also ships a binary called humpty [1] with two variants:\n\n`humpty-metal`\n\nfor Apple Silicon and a portable `humpty-cpu`\n\nthat should run anywhere (slowly).There are four subcommands that we'll use to explain how to build an agent up in OCaml:\nfirst [ list](#choose-the-right-deepseek-model) the available models and\n\n[one, then](#grab-the-deepseek-model-weights)\n\n`download`\n\n[with it statelessly, and then wrap that into an](#an-llm-is-a-stateless-request-reply-function)\n\n`chat`\n\n[.](#adding-state-to-make-an-agentic-ocaml-library)\n\n`agent`\n\n[1.1](#choose-the-right-deepseek-model) Choose the right Deepseek model\n\nBefore we can get started you'll first need the [open model weights](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash) downloaded.\n[ humpty list](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/bin/humpty.ml#L101-134)\nprints a the catalogue of available weights:\n\n``` bash\n$ humpty-metal list\nModels (download dir: /Users/avsm/.local/share/ds4)\n\n      TARGET                 ALIASES   DESCRIPTION\n  [ ] q2-imatrix             q2        2-bit Flash routed experts (~81 GB); for 96-128 GB RAM.\n  [*] q2-q4-imatrix          q2q4      Mixed Flash quant (~98 GB); higher quality for 128 GB.\n  [ ] q4-imatrix             q4        4-bit Flash routed experts (~153 GB); for 256 GB+ RAM.\n  [ ] pro-q2-imatrix         pro-q2    PRO q2 single file (~430 GB); for 512 GB RAM.\n\n[*] = present, [ ] = not downloaded\n```\n\nPick one based on how much RAM you have; I use `q2q4`\n\non my laptop (with 128GB RAM),\nand the extremely beefy `pro-q2`\n\non my Mac Studio (with 512GB RAM).\nThere are also split files for running the model distributed across several machines, which I'll\nskip here for now.\n\n[1.2](#grab-the-deepseek-model-weights) Grab the Deepseek model weights\n\nOnce you've chosen, [ humpty download q4](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/bin/humpty.ml#L66-97)\n(or\n\n`pro-q2`\n\n, or whichever) shells out to the Hugging Face CLI to fetch the GGUF.\nYou'll either need the [Huggingface CLI installed](https://huggingface.co/docs/huggingface_hub/en/guides/cli)or have\n\n[uvx](https://github.com/astral-sh/uv)in your path.\n\nOnce this gets doing go have a cup of tea while the gigabytes of LLM weights download, and then we'll start to build an agent from the camel up!\n\n[2](#building-an-agent-from-the-ground-up) Building an agent from the ground up\n\nI first want to pin down what an \"agent\" actually means, as the term seems to have\naccreted much mystique this year. The whole OCaml Deepseek stack is a small library you\ncan read through quickly, so let me build an agent up from scratch.\nThe code below links to the [Tangled source](https://tangled.org/anil.recoil.org/ocaml-deepseek/commit/1edcdad29a19924f988c7adef4343cb189dcb4a2).\n\n[2.1](#an-llm-is-a-stateless-request-reply-function) An LLM is a stateless request-reply function\n\nA basic LLM takes in a text prompt, performs inference on some weights, and generates a text reply back.\nTo illustrate this in our OCaml code, we need to load the model weights and spin up an\n`engine`\n\nwith a cache directory for the compiled Metal kernels (if using the\nApple GPU version):\n\n``` js\nlet engine = Deepseek.V4.create ~cache ~model ~domain_mgr ~sw () in\nV4.generate engine \"Explain monads in one sentence.\" ~on_token:print_string;\n- : unit\n\n=> Monads are a design pattern that allows you to chain operations together\n   while automatically handling extra behavior like error handling, state, or\n   side effects, by wrapping values in a context and providing a way to\n   transform and combine them.\n```\n\n[ Deepseek.V4.create](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/lib/v4.mli#L41-47)\nopens the\n\n[GGUF](https://huggingface.co/docs/hub/gguf)model file and, the first time it runs, materialises the embedded Metal shaders in the\n\n`cache`\n\n. Generating a\nreply is then a single call to `V4.generate`\n\nthat\nencodes the supplied prompt, runs a prefill, and samples one token at a time into the `on_token`\n\ncallback until the\nend-of-sequence marker.\nAll the inference is done in the Metal library in a separate OCaml domain,\nso we can continue to use other Eio fibres in our main application.You can try this single-shot request/response using [ humpty chat](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/bin/humpty.ml#L44-62), which keeps no memory between runs and can't take any action beyond showing the reply.\n\n``` bash\n$ humpty-metal chat 'Explain algebraic effects in OCaml 5 in one sentence'\n\n  Algebraic effects in OCaml 5 allow functions to suspend execution and invoke\n  user-defined handlers for operations (like state, exceptions, or generators)\n  via a lightweight, type-safe mechanism that integrates with the language's\n  existing type system and is used primarily for effectful programming, such as\n  with the new `Effect` library for handling delimited continuations.\n```\n\nA \"conversation\" is therefore just a list of role-tagged messages (e.g. system, user, assistant, tool) that we concatenate in our library into a prompt string for the LLM.\n\n[3](#how-the-stateless-llm-asks-for-effects-to-the-external-world) How the stateless LLM asks for effects to the external world\n\nThe single-step text-to-text function from earlier emits text in an agreed \"shape\" so we can figure out what to do next based on its output.\nDeepSeek has trained their model to understand a little markup language called [DSML](https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/encoding/encoding_dsv4.py), which looks something like this:\n\n```\n<｜DSML｜tool_calls>\n<｜DSML｜invoke name=\"edit\">\n<｜DSML｜parameter name=\"path\" string=\"true\">/tmp/x.c</｜DSML｜parameter>\n<｜DSML｜parameter name=\"line\" string=\"false\">42</｜DSML｜parameter>\n</｜DSML｜invoke>\n</｜DSML｜tool_calls>\n```\n\nDSML is a pseudo-XML language that's interspersed in the text responses from the agent.\nThose bars are actually the full-width vertical line `｜`\n\n(U+FF5C) and not an ASCII `|`\n\n. DeepSeek reserves the rarer codepoint for DSML's control tokens, so they can't be produced by ordinary text or code the model emits.\n\nWe've got a [DSML implementation in OCaml](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/main/dsml/lib/dsml.mli), which\n[parses the XML into an OCaml record type](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/dsml/lib/dsml.mli#L83-98):\n\n```\ntype thinking_mode = Chat | Thinking\n\ntype reasoning_effort = High | Max\n\ntype task = Action | Query | Authority | Domain | Title | Read_url\n\ntype tool_call = { id : string option; name : string; arguments : string }\n\ntype parsed_message = {\n  content : string;\n  reasoning_content : string;\n  tool_calls : tool_call list;\n}\n```\n\nParsing a raw text reply splits it into the visible content the user will see,\nthe model's hidden [reasoning content](https://en.wikipedia.org/wiki/Reasoning_model), and any tool calls it\nemitted along the way. A `tool_call`\n\nis just a string name plus its arguments\nas a JSON string, with an optional identifier to pair the result back up.\n\nThe other three types are knobs on *how* the model replies rather than what it\nreturns:\n\n- a\n`thinking_mode`\n\nof`Chat`\n\nanswers directly while`Thinking`\n\nreasons in a`<think>`\n\nblock first `reasoning_effort`\n\nturns that reasoning up to maximum at the cost of slower inference time`task`\n\nis a quick-instruction hint into DeepSeek's internal pipeline as to whether this turn is an`Action`\n\n, a`Query`\n\n, etc.\n\n[3.1](#making-custom-tool-functions-in-ocaml) Making custom tool functions in OCaml\n\nWe now need to define specific tools that the model knows about. We do this by defining a bidirectional codec that decodes the model's JSON/DSML arguments into a typed OCaml value, and renders that value back. Here's a simple [touch](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/lib/tool.mli#L11-20) tool:\n\n``` php\nlet touch =\n  let open Dsml.Codec in\n  Invoke.map \"touch\" (fun path -> path)\n  |> Invoke.param ~enc:Fun.id \"path\" string ~description:\"file to create\"\n  |> Invoke.seal\nin\nTool.v ~description:\"Create an empty file.\" touch\n  (fun path ->\n    Out_channel.with_open_text path ignore;\n    \"created \" ^ path)\n```\n\nWe just wrap the effect we are doing (in this case, just writing an empty file)\nwith the JSON metadata to let the model know *when* and *how* to invoke the\ntool as it works its way through the prompt. Unlike most policy languages, we\ndescribe these in plain text since we're dealing with an LLM, and it decides\nwhen the description of the tool should be applied in the conversation.\n\nThere's still no state here, but we can now use the DSML library to build up a full prompt string by keeping track of all the messages:\n\n``` php\nval encode_messages :\n  ?context:message list ->\n  ?drop_thinking:bool ->\n  ?add_default_bos_token:bool ->\n  ?reasoning_effort:reasoning_effort ->\n  thinking_mode ->\n  message list ->\n  string\n(** [encode_messages mode messages] renders the conversation to the prompt\n    string. [context] prepends an already-encoded prefix and suppresses the\n    leading token; [drop_thinking] (default true) drops reasoning from turns\n    before the last user message; [reasoning_effort] [Max] maximises reasoning.\n*)\n```\n\n[4](#adding-state-to-make-an-agentic-ocaml-library) Adding state to make an agentic OCaml library\n\nWe're now ready to add state to this! An \"agent\" is just a wrapper around the LLM that does three things:\n\n- remember the conversation so far for\n`encode_messages`\n\n- keep the engine's KV-cache warm via\n[a persistent session](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/lib/v4.mli#L95-108) - run the tool callbacks as they arrive from the LLM\n\nThe loop is expressed as a simple [OCaml event datatype](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/lib/agent.mli#L27-33):\n\n```\ntype event =\n  | Reasoning of string          (* the model's <think> text *)\n  | Content of string            (* a chunk of the reply *)\n  | Tool_call of Dsml.tool_call\n  | Tool_result of string * string\n  | Done\n\nval send : t -> on_event:(event -> unit) -> string -> unit\n```\n\nWhen the LLM responds each turn, plain text means the turn is `Done`\n\n. Tool\ncalls get looked up by their name, then run, and the results folded back into\nthe conversation as `tool`\n\nmessages. All the agent function does is just run\nthis to a fixed point until the model answers with text alone.\n\n[4.1](#writing-custom-tools-using-os-sandboxing-and-eio-capabilities) Writing custom tools using OS sandboxing and Eio capabilities\n\nHere's where the unikernel-style magic shows up though. Given that a tool is just something we define ourselves, then we can start to take advantage of OCaml itself! And in particular, I want better security and more fine-grained tool calls that are tailored to my applications and not generic shell scripts that are difficult to sandbox.\n\n[Eio is a library](/papers/2023-ocaml-eio) built over [OCaml 5's effects](/papers/2021-pldi-retroeff) that\nfollows an [object-capability](https://en.wikipedia.org/wiki/Object-capability_model)\ndiscipline to eliminate ambient authority where possible.\nIn our [Toolbox](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/lib/toolbox.mli#L4-22) module,\nwe define some example Eio tools:\n\n``` php\nval read  : dir:_ Eio.Path.t -> Tool.t\nval write : dir:_ Eio.Path.t -> Tool.t\nval dns   : net:_ Eio.Net.t -> Tool.t\nval bash  : proc:_ Eio.Process.mgr -> Tool.t\n```\n\nNotice that each OCaml signature here takes in a capability for that particular tool:\n\n`read`\n\nand`write`\n\ncan only access the directory you pass as`~dir`\n\nand nothing above it, since Eio usesto sandbox the call.[openat(2)](https://linux.die.net/man/2/openat)`dns`\n\nreaches the network only because it holds a net capability`bash`\n\nspawns processes only because it holds a[process manager](https://github.com/ocaml-multicore/eio/blob/main/README.md#running-processes).\n\nWhen using these from applications, we can select the exact sandboxing we need, or just write our own tool functions with application-specific logic. This is exactly what [ humpty agent](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/bin/humpty.ml#L154-201) does, confining the file and shell tools to a workspace directory you pass with\n\n`--dir`\n\n:\n\n```\n(* Sandbox the filesystem tools to [workspace] so that tools only have access there *)\nEio.Path.with_open_dir Eio.Path.(fs / workspace) @@ fun ws ->\nlet agent =\n  Agent.create engine ~system ~thinking:think\n    ~tools: [\n        Toolbox.read ~dir:ws;\n        Toolbox.write ~dir:ws;\n        Toolbox.dns ~net;\n        Toolbox.bash ~proc; ] in ...\n```\n\nThe `fs`\n\n, `net`\n\nand `proc`\n\nvalues all come from Eio's stanard environment, and\nits now up to the programmer to decide how to dole them out.\nIf you want a read-only agent, then just drop `write`\n\nand `bash`\n\n.\n\nSince a tool is just a [ Tool.v](https://tangled.org/anil.recoil.org/ocaml-deepseek/blob/1edcdad29a19924f988c7adef4343cb189dcb4a2/lib/tool.mli#L26)\nfunction over a codec and a handler, callers can add their own depending on the business\nlogic of the app. For example, I've now got some tools in this webserver that query the\nsize of the connection pool, another that can search the in-memory logs, and so on.\n\n[4.2](#managing-deterministic-and-reproducibility) Managing deterministic and reproducibility\n\nOne of the advantages of running a local model is that we have better control over the determinism of the inference. Aside from obvious factors like the weights and the inference code being the same, we normally still get different results from GPU based inference due to the parallelism.\n\nHowever, if we don't mind taking a slowdown, the CPU backend here supports saving and passing in the same seed:\n\n``` bash\n$ humpty-cpu chat 'tell me a joke about camels in one sentence' -v\nhumpty-cpu: [INFO] model: DeepSeek V4 Flash (vocab 129280)\nhumpty-cpu: [INFO] seed: 1690400691090126 (random; pass --seed N to reproduce)\nWhy did the camel cross the desert? To get to the other hump-side!\n\n$ humpty-cpu chat 'tell me a joke about camels in one sentence' -v\nhumpty-cpu: [INFO] model: DeepSeek V4 Flash (vocab 129280)\nhumpty-cpu: [INFO] seed: 1690392111533869 (random; pass --seed N to reproduce)\nWhy did the camel cross the desert? Because it was sick of the hump-drum of the same old sand dunes.\n\n$ humpty-cpu chat 'tell me a joke about camels in one sentence' --seed 1690400691090126\nWhy did the camel cross the desert? To get to the other hump-side!\n```\n\nI was surprised, however, to find that the deterministic seed also worked in the Metal backend!\n\n``` bash\n$ humpty-metal chat 'tell me a joke about camels in one sentence' -v\nhumpty-metal: [INFO] model: DeepSeek V4 Flash (vocab 129280)\nhumpty-metal: [INFO] seed: 2042575750328474 (random; pass --seed N to reproduce)\nWhy did the camel cross the desert? Because it was tired of the hump-drum of its daily routine.\n\n$ humpty-metal chat 'tell me a joke about camels in one sentence' -v\nhumpty-metal: [INFO] model: DeepSeek V4 Flash (vocab 129280)\nhumpty-metal: [INFO] seed: 2043271538873965 (random; pass --seed N to reproduce)\nWhy do camels never get stuck in traffic? Because they have built-in \"hump\" day passes.\n\n$ humpty-metal chat 'tell me a joke about camels in one sentence' -v --seed 2043271538873965\nhumpty-metal: [INFO] model: DeepSeek V4 Flash (vocab 129280)\nhumpty-metal: [INFO] seed: 2043271538873965\nWhy do camels never get stuck in traffic? Because they have built-in \"hump\" day passes.\n```\n\n[5](#implications-of-using-llms-as-a-library) Implications of using LLMs as a library\n\nWe've seen so far that the LLM model can be exposed as a mostly pure function; and that a tool is a function whose type says what it may touch; and that an agent is just a loop over them.\n\nCrucially, there's no need for serialisation protocols, REST APIs, MCP authentication and many of the other layers built over them unless the application wants it. One of the big advantages of unikernel-style libraries is that necessary functionality can be specialised at compile-time much more easily.\n\n[5.1](#building-a-safe-bastion-using-dikjstra-monads-or-refinements) Building a safe bastion using Dikjstra monads or refinements\n\nThis library approach is orthogonal to the idea of \"language integrated\" LLMs, which\ninvolve discharging verification conditions by having LLMs attempt to synthesise\nproofs of statements embedded within the source code.\nRanjit Jhala [observed](https://x.com/RanjitJhala/status/2065280989367357725) that:\n\n\"Language integrated\" is a drum I've been beating on for a while (e.g. with\n\n[refinement types]), but in the age of LLMs I wonder if itreallymatters, if the AIs are going to also be generating the proofs? --[Ranjit Jhala, June 2026]\n\n[Yaron Minsky](https://github.com/yminsky) [noted that](https://x.com/yminsky/status/2065403677309915570) *\"language-integrated\nassertions and modular specification look like a form of intelligence amplification for whatever is doing the proofs\"*. I also found\n[Shriram Krishnamurthi](https://cs.brown.edu/~sk/) and Flatt's [\"Type-Error Ablation and AI Coding Agents\"](https://arxiv.org/abs/2606.01522) paper that found richer type errors help an agent\nfix code, and that a type system helps it more than test failures do.\n\nAll this means is that we needn't stop at just using types to encode our safety properties. Since each tool is an ordinary OCaml function, nothing stops them being synthesised or checked by a proof assistant like [Fstar](https://www.fstar-lang.org/), so that a tool can ship with a machine-checked guarantee that it stays inside the policy its signature advertises.\n\nWe explored this last year in a [HOPE abstract](/papers/2024-hope-bastion) via [Dijkstra monads](https://arxiv.org/abs/1903.01237), since they let you reason precisely about the effects a computation is allowed to have. [Patrick Ferris](https://patrick.sirref.org) [Cyrus Omar](https://web.eecs.umich.edu/~comar/) and I sketched how to\nmodularise some more dependently typed policy reasoning in [Bastion](/papers/2024-hope-bastion). Eio's capabilities are really just a lightweight version of the sorts of things you can express in a full proof framework, and as Ranjit notes above, LLMs make it easier than ever to just pick the right specification language for the problem at hand.\n\nAnother obvious direction to take my library-agentic-harness is to link it in with\nOCaml's [compiler-libs](https://ocaml.org/manual/5.4/api/compilerlibref/Compiler_libs.html)\nto build a much more tightly integrated agent debugger that doesn't need to go\nvia CLI tooling!\n\n[5.2](#letting-a-service-watchand-mutateitself) Letting a service watch...and mutate...itself\n\nWhat else could we do with an LLM we can call as cheaply as any function, on hardware you own? (I'm assuming we have spare CPU/GPU here!)\n\nThe experiment I'm mid-way through is wiring this into my [zero-allocation OxCaml webserver](/notes/oxcaml-httpz), which already emits custom [runtime events](https://ocaml.org/manual/runtime-events.html) alongside OCaml's native GC and scheduler ones. Normally they pile up in a ring buffer nobody reads until something's already broken. The idea is to spend idle CPU on them, so when the server isn't busy, I hand a window of recent events to my agent function\nin a separate domain and check to see if the latency is drifting, or if that event is firing far more than yesterday (based on summary stats), and so on.\nWhether a local model is any good at this is something I don't know yet, but I'm enjoying experimenting.\n\nAs we discussed in the [rewilding the web](/notes/rewilding-the-web-report) workshop and our [Internet ecology](/papers/2025-internet-ecology) paper, self-hosted services unfortunately\ndon't just [run themselves](/notes/recoil-self-hosting-2026) and need tending to.\nI could therefore imagine building enough local tooling so that the agent harness would recompile\nand re-exec the binary autonomously in response [to external stimuli](/notes/internet-immune-system)...\n\nThe [code's here](https://tangled.org/anil.recoil.org/ocaml-deepseek) if\nyou'd like to pull it apart. I'd love to hear more about any improbable stunts or agentic harnesses you build yourself! In the future, I'll investigate expanding this out to CUDA and also beyond just Deepseek.", "url": "https://wpnews.pro/news/language-integrated-llms-as-an-ocaml-function", "canonical_source": "https://anil.recoil.org/notes/language-integrated-llms", "published_at": "2026-06-16 05:25:21+00:00", "updated_at": "2026-06-16 05:48:34.364036+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "ai-infrastructure", "developer-tools"], "entities": ["DeepSeek", "OCaml", "Anil Madhavapeddy", "Apple Metal", "Eio", "opam", "Hugging Face", "Antirez"], "alternates": {"html": "https://wpnews.pro/news/language-integrated-llms-as-an-ocaml-function", "markdown": "https://wpnews.pro/news/language-integrated-llms-as-an-ocaml-function.md", "text": "https://wpnews.pro/news/language-integrated-llms-as-an-ocaml-function.txt", "jsonld": "https://wpnews.pro/news/language-integrated-llms-as-an-ocaml-function.jsonld"}}