Scheme LLM Toolkit: Functional LLM Integration with Contracts
Table of Contents
- 1. Scheme LLM Toolkit
- 2. Related
1. Scheme LLM Toolkit
A composable, functional interface for LLM integration in Guile Scheme, emphasizing type safety through contracts and formal specification.
1.1. Repository
1.2. Philosophy
- 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
1.3. 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
1.4. Contract System
Fetched src/llm/core/contracts.scm directly from github.com/aygp-dr/scheme-llm-toolkit (main, 2026-10-08, 570 lines). Every combinator and function named below is a real export: flat-contract, and/c, or/c, ->/c, alist/c, listof/c, json-schema/c, contract-check, plus a <blame> record type (blame-positive/blame-negative/blame-contract/blame-value/blame-context) backing the "Blame Assignment" claim. The Example Usage code block's forms (flat-contract, and/c, ->/c, alist/c) match the real API signatures, not simplified stand-ins.
The contract system is inspired by Racket's approach to design-by-contract:
1.4.1. 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
1.4.2. 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))))
1.4.3. 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 |
1.5. Provider Abstraction
Fetched src/llm/core/provider.scm and src/llm/providers/openai-provider.scm directly from github.com/aygp-dr/scheme-llm-toolkit (main, 2026-10-08). register-openai-provider!, get-provider, provider-complete, and provider-capabilities all exist with these exact names; provider.scm exports a #:capabilities field validated against a *standard-capabilities* list. The code example is a real call sequence, not illustrative pseudo-code.
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 ...)
1.6. 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))
1.7. FreeBSD Compatibility
Fetched the project's Makefile directly from github.com/aygp-dr/scheme-llm-toolkit (main, 2026-10-08) and confirmed each adaptation: it branches on `uname -s`, and on FreeBSD sets `SHELL := /usr/local/bin/bash` and `GUILE ?= guile3` (vs. plain `guile` elsewhere); `bin/guile-llm` exists in the repo tree as stated.
# 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)
1.8. Formal Specification
1.8.1. 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))
=========================================================================
1.8.2. Lean4 Style
structure Provider where
name : String
complete : String → IO (Except Error Response)
-- Proof obligations
complete_deterministic : ∀ p, complete p = complete p
1.8.3. 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
1.9. Project Status
Checked each row against the actual repo tree (github.com/aygp-dr/scheme-llm-toolkit, main, fetched 2026-10-08), not the stale CHANGELOG.md (still pinned at v0.1.0 / 2025-08-05, which lists OpenAI/Anthropic as future roadmap items). The code has since caught up with this note: src/llm/providers/ has anthropic.scm (281 lines) + anthropic-provider.scm, openai.scm (273 lines) + openai-provider.scm, and ollama.scm + ollama-provider.scm, all substantial, non-stub implementations. src/llm/core/tools.scm exports make-tool, tools->openai-format, and tools->anthropic-format, and both openai.scm and anthropic.scm accept a #:tools / #:tool-choice parameter -- confirming "Function Calling | Complete | Cross-provider". A full repo-tree search found no cli/, rag/, or embedding-related path, confirming "CLI Tool" and "RAG Utilities" are still correctly marked Planned, not built.
| 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 |
1.10. 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
1.11. 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)"
1.12. 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)))
1.13. Conclusion
2. Related
- guile-sage: Building an AI Agent in Scheme – an agent built in Guile on the same footing
- Building AI Agent Tool Systems: A Multi-Provider Architecture – provider abstraction, from the tool side
- Racket – Racket, where the contracts come from
- Scheme: Notes and Examples – the language notes