Scheme LLM Toolkit: Functional LLM Integration with Contracts

Table of Contents

Scheme LLM Toolkit

A composable, functional interface for LLM integration in Guile Scheme, emphasizing type safety through contracts and formal specification.

Philosophy

The toolkit follows several key principles:

  1. Composability - Functions compose naturally using standard Scheme idioms
  2. Provider Abstraction - Unified interface across LLM providers (Ollama, OpenAI, Anthropic)
  3. Contracts as Documentation - Runtime contracts serve as executable specifications
  4. Formal Methods Integration - Contracts bridge to TLA+, Lean4, and OpenAPI specifications

Architecture

src/llm/
├── core/
│   ├── provider.scm     # Provider abstraction layer
│   ├── contracts.scm    # Racket-inspired contract system
│   └── tools.scm        # Function calling / tool use
├── providers/
│   ├── ollama.scm       # Local Ollama API
│   ├── openai.scm       # OpenAI API
│   └── anthropic.scm    # Anthropic Claude API
└── utils/
    ├── http.scm         # HTTP client (curl-based)
    └── json.scm         # JSON parsing/generation

Contract System

The contract system is inspired by Racket's approach to design-by-contract:

Core Concepts

  • Flat Contracts - Simple predicates (string?, number?)
  • Combinators - Compose contracts (and/c, or/c, not/c)
  • Function Contracts - Specify domain/range (->/c)
  • Structural Contracts - Validate data shapes (alist/c, listof/c)
  • Blame Assignment - Identify who violated the contract

Example Usage

(use-modules (llm core contracts))

;; Define a flat contract
(define positive?
  (flat-contract (lambda (x) (> x 0))
                 #:name "positive?"))

;; Contract combinators
(define non-empty-string?
  (and/c (flat-contract string?)
         (flat-contract (lambda (s) (> (string-length s) 0)))))

;; Function contract
(define string->number/c
  (->/c (flat-contract string?)
        (flat-contract number?)))

;; Structural contract for LLM messages
(define message/c
  (alist/c
   #:required `((role . ,role/c)
                (content . ,string/c))
   #:optional `((name . ,string/c))))

Formal Methods Mapping

Racket Concept TLA+ Equivalent Lean4 Equivalent OpenAPI
Flat contract State predicate Prop type
Function contract Action spec Function type operation
Blame Error trace Error monad 4xx/5xx

Provider Abstraction

All providers implement a common interface:

(use-modules (llm core provider)
             (llm providers openai-provider))

;; Register a provider
(register-openai-provider!
 #:api-key (getenv "OPENAI_API_KEY")
 #:model "gpt-4o")

;; Use through abstraction
(let ((provider (get-provider 'openai)))
  (provider-complete provider "Explain recursion briefly."))

;; Provider capabilities are declared
(provider-capabilities provider)
;; => (streaming chat embeddings function-calling ...)

Function Calling / Tool Use

(use-modules (llm core tools))

;; Define a tool
(define weather-tool
  (make-tool
   #:name "get_weather"
   #:description "Get current weather for a location"
   #:parameters '((type . "object")
                  (properties . ((city . ((type . "string"))))))
   #:handler (lambda (city)
               (format #f "Weather in ~a: sunny" city))))

;; Convert to provider format
(tools->openai-format (list weather-tool))
(tools->anthropic-format (list weather-tool))

FreeBSD Compatibility

The toolkit includes first-class FreeBSD support:

# FreeBSD setup
pkg install guile3 curl bash

# guile-json must be built from source
gmake install-guile-json

# Run tests
gmake test

Key adaptations:

  • Auto-detects OS and sets GUILE=guile3 on FreeBSD
  • Uses /usr/local/bin/bash instead of /bin/bash
  • Provides bin/guile-llm wrapper for portable execution
  • Build script for guile-json (not in FreeBSD packages)

Formal Specification

The project includes formal specifications mapping contracts to:

TLA+ Style

---------------------------- MODULE Provider ----------------------------
VARIABLES providerState, requestQueue

TypeInvariant ==
    /\ providerState \in [Providers -> {"ready", "busy", "error"}]

CanAcceptRequest(p) ==
    providerState[p] = "ready"

Safety ==
    \A p \in Providers:
        providerState[p] = "error" =>
            ~(\E r \in Responses: Complete(p, _, r))
=========================================================================

Lean4 Style

structure Provider where
  name : String
  complete : String  IO (Except Error Response)

  -- Proof obligations
  complete_deterministic :  p, complete p = complete p

JSON Schema / OpenAPI

;; Convert JSON Schema to contract
(define user/c
  (json-schema/c
   '((type . "object")
     (required . ("name" "email"))
     (properties
      . ((name . ((type . "string")))
         (email . ((type . "string"))))))))

(contract-check user/c '((name . "Alice") (email . "a@b.com")))
;; => #t

Project Status

Component Status Notes
Ollama Provider Complete Local LLM support
OpenAI Provider Complete Including function calling
Anthropic Provider Complete Claude API support
Contract System Complete Racket-inspired
Function Calling Complete Cross-provider
CLI Tool Planned Interactive chat
RAG Utilities Planned Embeddings + search

References

Installation

# Clone
git clone https://github.com/aygp-dr/scheme-llm-toolkit
cd scheme-llm-toolkit

# Check system
gmake check-system  # FreeBSD
make check-system   # Linux/macOS

# Install dependencies
gmake install-guile-deps

# Test
gmake test

# Use
bin/guile-llm -c "(use-modules (llm core provider)) (display 'ok)"

Why Scheme for LLMs?

  1. Homoiconicity - Code as data enables powerful prompt templating
  2. Macros - DSLs for prompt engineering compile to efficient code
  3. REPL-driven - Interactive development with live LLM connections
  4. Functional Composition - Pipeline LLM calls naturally
  5. Formal Methods Heritage - Natural fit for specification and verification
;; S-expression prompts compose naturally
(define code-review-prompt
  `(system "You are a code reviewer")
    (user ,(format #f "Review: ~a" code)))

;; Functional pipelines
(define analyze-and-fix
  (compose
   (curry provider-complete provider)
   (curry format-prompt 'fix-bugs)
   extract-issues
   (curry provider-complete provider)
   (curry format-prompt 'analyze)))

Conclusion

The Scheme LLM Toolkit demonstrates that functional programming principles—composition, immutability, and formal contracts—provide a robust foundation for LLM integration. By treating contracts as executable specifications that bridge to formal methods, we gain both runtime safety and documentation that can inform verification efforts.

Author: Jason Walsh

j@wal.sh

Last Updated: 2025-12-22 23:21:31

build: 2026-04-17 18:37 | sha: 792b203