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.
Repository
Philosophy
The toolkit follows several key principles:
- Composability - Functions compose naturally using standard Scheme idioms
- Provider Abstraction - Unified interface across LLM providers (Ollama, OpenAI, Anthropic)
- Contracts as Documentation - Runtime contracts serve as executable specifications
- 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=guile3on FreeBSD - Uses
/usr/local/bin/bashinstead of/bin/bash - Provides
bin/guile-llmwrapper 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
- Racket Contracts: https://docs.racket-lang.org/reference/contracts.html
- TLA+ Specification Language: https://lamport.azurewebsites.net/tla/tla.html
- Lean4 Theorem Prover: https://lean-lang.org/
- OpenAPI Specification: https://spec.openapis.org/oas/latest.html
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?
- Homoiconicity - Code as data enables powerful prompt templating
- Macros - DSLs for prompt engineering compile to efficient code
- REPL-driven - Interactive development with live LLM connections
- Functional Composition - Pipeline LLM calls naturally
- 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.
