Vibe Coding in Scheme

2025/06/15

(Everything in this post licensed Apache 2.0)

Lisp is well known for the read-eval-print loop. The advent of AI now gives us the openaiApiCall-eval-print loop, commonly known as “vibe coding.”

The following code implements vibe coding in CHICKEN Scheme using an OpenAI compatible API. It works with OpenAI GPT models and should also work with llama-server. The following egg are required:

chicken-install r7rs http-client medea box intarweb uri-common openssl

API

If the source file is loaded in a directory with a file named openai-api-key, then the contents of that file are loaded as the API key for API requests.

Global Variables

Global variables are a sign of quality.

Each global is a parameter that contains a box. To get the current value of the global in a dynamic environment, use unbox (name). To set the value of the global in the dynamic environment, use (set-name! value). The globals can be parameterize-d in a dynamic environment.

Example:

(with-input-from-file "openapi-secret-key"
  (lambda (port) (set-api-key! (read-string))))

Globals:

AI Calls

(ask-ai prompt)

Returns a string with the response fron the AI.

(read-ai prompt)

Uses read-prompt before prompt. Returns response from AI as Scheme datum.

(vibe commands ...)

Syntax. Evaluates the response to commands ... as Scheme code.

(define-vibe name commands ...)

Evaluates the response to commands ... as Scheme code and stores the result in name.

TODO: metavibe, which is a macro that makes an API call and splices in the result of the API call.

Source

(import r7rs (chicken file) http-client medea box intarweb uri-common openssl)

(define-syntax define-global
  (syntax-rules ()
    ((_ name default setter)
     (begin
       (define name (make-parameter default box))
       (define (setter value)
         (set-box! (name) value))))))

(define-global api-key #f set-api-key!)
(define-global api-base-url "https://api.openai.com" set-api-base-url!)
(define-global model "gpt-3.5-turbo-instruct" set-model!)
(define-global max-tokens 1024 set-max-tokens!)
(define-global read-prompt
  "Return responses in CHICKEN 5 Scheme. Do not add commentary or markup, just return the code.\n My request is:"
  set-read-prompt!)

(when (file-exists? "openai-api-key")
  (with-input-from-file "openai-api-key"
    (lambda () (set-api-key! (read-string)))))

(define (ask-ai prompt)
  (define request
    (make-request
     uri: (uri-reference (string-append (unbox (api-base-url)) "/v1/completions"))
     method: 'POST
     headers: (headers
               `((Content-Type #("application/json" raw))
                 (Authorization #(,(string-append "Bearer " (unbox (api-key))) raw))))))
  (call-with-input-request request
    (json->string `((model . ,(unbox (model)))
                    (prompt . ,prompt)
                    (max_tokens . ,(unbox (max-tokens)))))
    (lambda (port)
      (let ((json (read-json port)))
        (cond
          ((assq 'choices json)
           => (lambda (pair)
                (let ((choice (vector-ref (cdr pair) 0)))
                  (cdr (assq 'text choice)))))
          (else #f))))))

(define (read-ai prompt)
  (let ((response (ask-ai (string-append (unbox (read-prompt)) prompt))))
    (display response (current-error-port))
    (newline (current-error-port))
    (call-with-port (open-input-string response)
      (lambda (port)
        (guard (ex
                ((read-error? ex)
                 (error "truncated response" prompt response ex))
                (else (raise ex)))
          (read port))))))

(define-syntax vibe
  (syntax-rules ()
    ((vibe prompt ...)
     (eval (read-ai (string-append (string-append prompt "\n") ...))))))

(define-syntax define-vibe
  (syntax-rules ()
    ((define-vibe name prompt ...)
     (define name (vibe prompt ...)))))

Examples

Real programmers run this code outside of a container.

(define-vibe factorial
   "Write a lambda implementing factorial using letrec.")
(= (factorial 5) 120)

(define-vibe treezip
  "Write a lambda converting a LISP tree into a list using letrec.")
(equal? (treezip '(1 . ((2 . 3) . (4 . 5)))) '(1 2 3 4 5))

(define-vibe reverse
 "Write a lambda reversing a list using letrec.")
(equal? (reverse '(1 2 3 4 5)) '(5 4 3 2 1))

(vibe "Output the contents of the text file 'openai-api-key' using only R5RS procedures.")