Table of Contents
Fetching ...

Oracular Programming: A Modular Foundation for Building LLM-Enabled Software

Jonathan Laurent, André Platzer

TL;DR

Oracular Programming introduces a modular foundation for integrating inductive LLMs with explicit computations by segregating core problem-solving logic from search and prompting logic. The proposed triad of languages—Strategy, Policy, and Demonstrations—enables strong modularity and evolvability; a Python-based framework, Delphyne, implements these ideas with tooling for interactive demonstrations and visualization. The approach is demonstrated through case studies on advanced search with invariant synthesis, learning via search reflection in Lean, and universal query prompts, showcasing substantial improvements in efficiency, self-improvement, and reusability. Collectively, the work offers a principled path toward building reliable, scalable LLM-enabled software and provides practical tooling for practitioners to compose, test, and evolve oracular programs. The concept of search reflection, coupled with the separation of core and search logic, points to fruitful directions for self-improving AI systems that leverage both explicit computation and inductive reasoning.

Abstract

Large Language Models can solve a wide range of tasks from just a few examples, but they remain difficult to steer and lack a capability essential for building reliable software at scale: the modular composition of computations under enforceable contracts. As a result, they are typically embedded in larger software pipelines that use domain-specific knowledge to decompose tasks and improve reliability through validation and search. Yet the complexity of writing, tuning, and maintaining such pipelines has so far limited their sophistication. We propose oracular programming: a foundational paradigm for integrating traditional, explicit computations with inductive oracles such as LLMs. It rests on two directing principles: the full separation of core and search logic, and the treatment of few-shot examples as grounded and evolvable program components. Within this paradigm, experts express high-level problem-solving strategies as programs with unresolved choice points. These choice points are resolved at runtime by LLMs, which generalize from user-provided examples of correct and incorrect decisions. An oracular program is composed of three orthogonal components: a strategy that consists in a nondeterministic program with choice points that can be reified into a search tree, a policy that specifies how to navigate this tree with the help of LLM oracles, and a set of demonstrations that describe successful and unsuccessful tree navigation scenarios across diverse problem instances. Each component is expressed in a dedicated programming language and can be independently improved or substituted. We address the key programming language design challenges of modularly composing oracular programs and enforcing consistency between their components as they evolve.

Oracular Programming: A Modular Foundation for Building LLM-Enabled Software

TL;DR

Oracular Programming introduces a modular foundation for integrating inductive LLMs with explicit computations by segregating core problem-solving logic from search and prompting logic. The proposed triad of languages—Strategy, Policy, and Demonstrations—enables strong modularity and evolvability; a Python-based framework, Delphyne, implements these ideas with tooling for interactive demonstrations and visualization. The approach is demonstrated through case studies on advanced search with invariant synthesis, learning via search reflection in Lean, and universal query prompts, showcasing substantial improvements in efficiency, self-improvement, and reusability. Collectively, the work offers a principled path toward building reliable, scalable LLM-enabled software and provides practical tooling for practitioners to compose, test, and evolve oracular programs. The concept of search reflection, coupled with the separation of core and search logic, points to fruitful directions for self-improving AI systems that leverage both explicit computation and inductive reasoning.

Abstract

Large Language Models can solve a wide range of tasks from just a few examples, but they remain difficult to steer and lack a capability essential for building reliable software at scale: the modular composition of computations under enforceable contracts. As a result, they are typically embedded in larger software pipelines that use domain-specific knowledge to decompose tasks and improve reliability through validation and search. Yet the complexity of writing, tuning, and maintaining such pipelines has so far limited their sophistication. We propose oracular programming: a foundational paradigm for integrating traditional, explicit computations with inductive oracles such as LLMs. It rests on two directing principles: the full separation of core and search logic, and the treatment of few-shot examples as grounded and evolvable program components. Within this paradigm, experts express high-level problem-solving strategies as programs with unresolved choice points. These choice points are resolved at runtime by LLMs, which generalize from user-provided examples of correct and incorrect decisions. An oracular program is composed of three orthogonal components: a strategy that consists in a nondeterministic program with choice points that can be reified into a search tree, a policy that specifies how to navigate this tree with the help of LLM oracles, and a set of demonstrations that describe successful and unsuccessful tree navigation scenarios across diverse problem instances. Each component is expressed in a dedicated programming language and can be independently improved or substituted. We address the key programming language design challenges of modularly composing oracular programs and enforcing consistency between their components as they evolve.

Paper Structure

This paper contains 73 sections, 3 equations, 29 figures, 1 table.

Figures (29)

  • Figure 1: Naive Definition for a Search Tree. A tree producing values of type a (Tree a) can be either a success leaf, a failure leaf that represents a contract violation, or a branching node that contains a query and one subtree for every possible answer to this query. A query represents a question being asked to an external oracle and is defined by a prompt along with an answer-parsing function. Note that b is existentially quantified in the definition of a branching node, meaning that each such node can have children indexed by a different, locally-defined type (perhaps surprisingly, existential types are introduced in Haskell using the forall keyword. This is because any constructor type $(\exists \alpha \, T(\alpha)) \rightarrow \tau$ is isomorphic to $\forall \alpha \, (T(\alpha) \rightarrow \tau)$).
  • Figure 2: A Naive Language for Defining Search Trees. The tree type defined in Figure \ref{['fig:naive-tree']} can be equiped with a monadic structure, allowing trees to be defined directly and naturally using Haskell's do-notation (see Figure \ref{['fig:naive-strategy-example']} for an example). For readers unfamiliar with monads, understanding this technical definition is not crucial: what matters more is an intuitive understanding of how a tree (as defined in Figure \ref{['fig:naive-tree']}) can be defined via a nondeterministic program (as the one in Figure \ref{['fig:naive-strategy-example']}).
  • Figure 3: A Minimal Strategy for Program Synthesis. This strategy is expressed using the naive strategy language defined in Figure \ref{['fig:naive-strategy']}. To generate a program that provably meets specification spec, strategy generateProg first issues a query to conjecture a program (via conjectureProg, whose type but not definition is provided), then issues a query to obtain a proof that the program indeed meets this specification (via generateProof), and finally ensures that the proof is correct. For all values of its spec argument, generateProg produces a tree with two levels of branching, followed by either success or failure leaves.
  • Figure 4: Example of a Modular Strategy For Program Synthesis. Strategies are nondeterministic programs that can be reified into search trees. They can issue queries to be answered by external oracles (queries are represented by diamonds $\diamond$ and have constructors starting with uppercase letters). Branching, value and failure nodes are labeled B, F, and V respectively. Horizontal lines denote nesting while other lines denote parent-child relations. Child edges and success leaves are decorated with local value references, with numbers being used as shortcuts for individual query answers. Grayed-out elements can be ignored on first reading.
  • Figure 5: A Modular Strategy Language, as a Haskell Monadic DSL. Section \ref{['sec:extensible-strategy-language']} demonstrates how to extend this language with new effect types beyond Fail, Branch, and Value. The $\in$ operator expresses membership of a type within a list of types swierstra2008data. An ensure function can be defined from fail, as in Figure \ref{['fig:naive-strategy']}.
  • ...and 24 more figures