Skip to content

DSL

Internally, Equistamp uses a DSL for sending tasks, parsing responses and grading replies. This DSL is basically a small subset of Clojure, without lambdas, macros, loops and most of the goodies one would expect. This lobotomization is intentional, to limit the harm of bad actors getting access to our systems.

Feel free to reach out to us at reports@equistamp.com if you need more functionality to be added. We won't add all requests, but missing core Clojure functions will probably be added quickly.

Tester

You can use our tester endpoint to check DSL code. That endpoint will return the result of running valid DSL code in order to make sure your code is correct. You can also just use any Clojure interpreter - as long as you only use symbols mentioned on this page, it should work.

Syntax

Data Types

Refer to the Clojure docs for a exhaustive description of the allowed types. Not all of the Clojure types will be supported, but the following is a short intro. One thing worth remembering, is that other than atoms, all data types are immutable, so calling a function that modifies its arguments will return a new object with the updated data, rather than modifying the passed in object.

  • Symbols begin with a non-numeric character and can contain alphanumeric characters and *, +, !, -, _, ', ?, <, > and =
  • nil - is nothing/no value and is tests logical false. The empty list (()) also evaluates to nil
  • Integers work as expected, at least if not too large (under 2^32 should be fine)
  • Floats also work as expected
  • Strings are always between double quotes, not apostrophes. So "asd" is a valid string, but 'asd' is not
  • Keywords start with a colon and always evaluate to the same object. So :key is always exactly the same as :key - this does not hold true with strings, e.g. "asd" is equal "asd", but they are different objects. Keywords also can work as the get function, so (:bla {:bla 123}) will return 123, as it's getting the value of :bla from the map {:bla 123}
  • The booleans are true and false
  • Lists are zero or more values in parentheses, so all of the following are valid lists:
    • () - this is functionally the same as nil
    • (1 2 3)
    • ((3 2) (3 (4 5) (4 5)) (5 3))
    • (+ 1 2 3 4) - this is equivalent to 1 + 2 + 3 + 4
  • Vectors are zero or more values in square brackets, e.g. [1 2 3 4]
  • Maps are mapping of keys to values, so the same as Python dicts. Maps are defined by zero or more key value pairs enclosed in braces. The keys and values can be any forms, and commas are treated as whitespace, so will be ignored. Maps also act as the get function, so ({:bla 123} :bla) will return 123, as this call amounts to "return the mapping of the :bla keyword". The following are valid mappings:
    • {}
    • {:bla 123}
    • {"key 1" 312 "key 2" 423, "key 4" 342}
    • {"key 1" 312, "key 2" 423, "key 4" 342} - this map is exactly the same as the previous one, as commas are ignored
    • {:bla (1 (3 4))}
  • Sets are zero or more forms enclosed in braces preceeded by #. Sets can act as function that return whether it contains a given key, e.g. (#{1 2 3} 1) returns true, while (#{1 2 3} 42) will return false. The following are valid sets:
    • #{}
    • #{1 2 3 4}
    • #{1 1 1 1 1 1 1 1 1 1 1 1 1} - this is exactly the same as #{1}
  • Atoms are used for mutability. They are basically a container for an arbitrary value, which can be modified with the appropriate functions (mainly deref, reset! and swap!). Atoms are created with the (atom <value>) function, and read with (deref <atom>), or @<atom> for convenience. The following are some examples:

    • (atom 123) - creates an atom containing the literal 123
    • (deref my-atom) - returns the value of my-atom
    • (deref (atom 312)) - returns the value of the created atom, i.e. 312
    • @my-atom - returns the value of my-atom

Truthiness

In Clojure, all values are logically true or false. The only "false" values are false, nil and the empty list () (which is the same as nil) - all other values are logically true. This can be a bit of a gotcha when checking for emptiness - in which case use the seq function, which will return nil for empty collections, e.g. (seq "") => nil.

Evaluation

Clojure is a LISP, so every expression evaluates to something. Basic types (like numbers or strings) evaluate to themselves. Lists are evaluated as function calls, where the first element is the function, and all the other elements are the function's arguments. Here are some examples:

  • 123 -> 123
  • "bla" -> "bla"
  • (+ 1 2 3 4) -> 10
  • (+ (+ 1 3) (- 10 6) (* 2 2)) -> 12
  • (some-function "arg 1" "arg 2") -> the results of calling some-function with "arg 1" and "arg 2", so the equivalent of someFunction("arg 1", "arg 2") in more known languages like Python

Special forms

There are a few special forms in the language, everything else is handled by function. These special forms are:

if

Conditional execution. Defined as (if <test> <positive branch> <optional negative branch>). First <test> is evaluated. If the test returns something that is non false (i.e. anything other than nil, () or false) then the result of evaluating <positive branch> will be returned. Otherwise (i.e. if <test> returns (), nil or false) then the result of evaluating <negative branch> will be returned. Only one branch will ever be evaluated. Some examples are:

  • (if true 1 2) -> 1
  • (if false 1 2) -> 2
  • (if true 1) -> 1
  • (if false 1) -> nil
  • (if (+ 1 2) 3) -> 3
  • (if (> 2 3) "larger" "smaller") -> "smaller"
  • (if (> val 3) "larger" "smaller") -> if the result of evaluating val is larger than 3, this will return "larger", otherwise it'll return "smaller"

do

Execute a group of zero or more clauses, returning the result of evaluating the last clause. This is mainly useful for operations with side effects. Some examples are:

  • (do) -> nil
  • (do 1 2 3) -> 3
  • (do (first-operation) (second-operation) (third-operation)) -> the result of (third-operation)

let

let binds symbols to values in a "lexical scope". A lexical scope creates a new context for names, nested inside the surrounding context. Names defined in a let take precedence over the names in the outer context. The syntax is

(let [[symbol1 binding1]
      [symbol2 binding2]
      (...)]
  (operation1)
  (operation2)
  (...))

Some examples are:

  • (let [[bla 12]] (+ 1 bla)) -> 13
  • (let [] (clause1) (clause2)) -> the same as (do (clause1) (clause 2))
  • (let [[one 1] [two 2] [three 3]] (+ one two three)) -> 6

throw

Throws the result of evaluating expr. The available exceptions are EvaluationError, ValueError, RateLimitError, ServerError and RetryError, all of which accept a single string. Some examples:

  • (throw (ValueError "help!"))
  • (throw (if val (ServerError val) (ValueError "no value found"))

try

Catches exceptions. Syntax is (try expr* catch-clause* finally-clause?), where: * catch-clause → (catch classname name expr*) * finally-clause → (finally expr*)

The exprs are evaluated and, if no exceptions occur, the value of the last expression is returned. If an exception occurs and catch-clauses are provided, each is examined in turn and the first for which the thrown exception is an instance of the classname is considered a matching catch-clause. If there is a matching catch-clause, its exprs are evaluated in a context in which name is bound to the thrown exception, and the value of the last is the return value of the function. If there is no matching catch-clause, the exception propagates out of the function. Before returning, normally or abnormally, any finally-clause exprs will be evaluated for their side effects. Example:

(try
  (+ 1 2)
  (/ 123 0)
  (catch ValueError e (str "caught: " e))
  (finally (POST "http://my.endpoint")))

Core functions

All other functionality is provided via functions that are available in the default namespace. Most of these are taken directly from Clojure, so if in doubt please reference ClojureDocs.

Base functions

identity

Returns its argument, e.g. (identity 123) -> 123. This is very useful e.g. when filtering items, as you can use it to remove any falsey items. Returns nil if no arguments provided. Examples:

  • (identity 123) -> 123
  • (identity "123") -> "123"
  • (identity nil) -> nil
  • (identity) -> nil

=

Checks equality. Receives 0 or more values, and will return true if they are all equal to each other. See the docs for specifics. Examples:

  • (=) -> true
  • (= false) -> true
  • (= false false) -> true
  • (= false false (= 1 2)) -> true
  • (= 1 2 3 4) -> false

or

Logical or. Receives 0 or more values, and will return the first value that evaluates truthy, going from left to right. Examples:

  • (or) -> nil
  • (or 1 2 3 4) -> 1
  • (or false nil true false nil) -> true

and

Logical and. Receives 0 or more values, and will return the first value going from left to right that evaluates falsey, or the last value if all are truthy. Examples:

  • (and) -> nil
  • (and 1 2 3 4) -> 4
  • (and false nil true false nil) -> nil

not

Logical negation. Will return true for false, nil or () and false for anything else. Examples:

  • (not false) -> true
  • (not nil) -> true
  • (not '()) -> true
  • (not true) -> false
  • (not []) -> false
  • (not {}) -> false
  • (not "bla bla bla") -> false

Collection functions

seq

Returns a seq on the collection. If the collection is empty, returns nil. (seq nil) also returns nil. Examples are:

  • (seq nil) -> nil
  • (seq '()) -> nil
  • (seq []) -> nil
  • (seq {}) -> nil
  • (seq (set)) -> nil
  • (seq "") -> nil

  • (seq "123") -> (1 2 3)

  • (seq (1 2 3)) -> (1 2 3)
  • (seq [1 2 3]) -> [1 2 3]
  • (seq {:a 1, :b 2}) -> ([:a 1] [:b 2])

count

Returns the number of items in the collection. Examples are:

  • (count nil) -> 0
  • (count '()) -> 0
  • (count []) -> 0
  • (count (set )) -> 0
  • (count (1 2 3)) -> 3
  • (count "abcde") -> 5

set

Makes a set out of the provided collection. The collection must contain hashable items. Examples are:

  • (set nil) -> #{}
  • (set [1 2 3 4]) -> #{1 2 3 4}

Sets can also be used as functions, so this is a useful way of checking whether a list contains an item, e.g.:

  • ((set [1 2 3]) 2) -> true
  • ((set [1 2 3]) 42) -> false

get

Returns the item at the given key in the provided collection. In the case of lists and vectors, key should be the index of the desired item. The syntax is (get <collection> <key> <optional default value>). Both keywords and maps can be used instead of the get function. Examples are:

  • (get [1 2 3] 0) -> 1
  • (get {:bla 1} :bla) -> 1
  • (get {:bla 1} :not-found) -> nil
  • (get {:bla 1} :not-found 432) -> 423
  • ({:bla 2} :bla) -> 2
  • ({:bla 2} :not-found) -> nil
  • ({:bla 2} :not-found 43) -> 43
  • (:bla {:bla 2}) -> 2
  • (:not-found {:bla 2}) -> nil
  • (:not-found {:bla 2} "default") -> "default"

get-in

Returns the value at the provided path. Syntax is (get-in <collection> <path> <optional default value>). Examples are:

  • (get-in {} [:a :b :c]) -> nil
  • (get-in {:nested [1 2 3]} [:nested 2]") -> 3

assoc

Sets a value at the given key. Syntax is (assoc <collection> <key> <value>). In the case of lists and vectors, <key> should be an appropriate index between 0 and the length of the collection. In the case of lists and vectors, all numerical keys will be floored to integers, and anything else will raise an error. Examples are:

  • (assoc [1 2 3] 1 "bla") -> [1 "bla" 3]
  • (assoc {:key1 1 :key2 2} :key1 32) -> {:key1 32 :key2 2}
  • (assoc {:key1 1 :key2 2} :key3 32) -> {:key1 1 :key2 2 :key3 32}
  • (assoc [1 2 3] -23 1) -> will raise an error
  • (assoc [1 2 3] 100 1) -> will raise an error

assoc-in

Sets a value at the given path in a nested structure. Syntax is (assoc <collection> <path> <value>). If a key cannot be found in a map, a new map will be added to contain it. The same limitations apply in the case of lists and vectors that apply for assoc. Examples are:

  • (assoc-in [1 2 3] [1] "bla") -> [1 "bla" 3]
  • (assoc-in {:outer {:inner {:a 2}}} [:outer :inner :b] "bla") -> {:outer {:inner {:a 2 :b "bla"}}}
  • (assoc-in {} [:outer :inner :b] "bla") -> {:outer {:inner {:b "bla"}}}

every?

Goes over the provided <collection> and returns true if the <predicate> function evaluates as truthy for all values. Syntax is (every? <predicate> <collection>). Some examples:

  • (every? #{1 2} [1 2 3 4]) -> false
  • (every? (set list-to-check-against) list-to-check) -> generic way of checking whether two lists have the same elements

not-any?

Goes over the provided <collection> and returns true if the <predicate> function evaluates as truthy for none of the values. Syntax is (not-any? <predicate> <collection>). Some examples:

  • (not-any? #{1 2} [1 2 3 4]) -> false
  • (not-any? #{41} [1 2 3 4]) -> true

some

Goes over the provided <collection> and returns true if the <predicate> function evaluates as truthy for any value. Syntax is (some <predicate> <collection>). Some examples:

  • (some #{1 2} [1 2 3 4]) -> true
  • (some (set list-to-check-against) list-to-check) -> generic way of checking whether two lists have common elements

Functional functions

apply

Returns the result of calling <func> with the provided <args>, where <args> is a collection. Syntax is (apply <func> a b c ... args). Examples:

  • (apply +) -> 0
  • (apply + [1 2 3]) -> 6
  • (apply + 1 [1 2 3]) -> 7
  • (apply + 1 2 3 4 [1 2 3]) -> 16
  • (apply + 1 2) -> will raise an error, as the last argument must be a collection

partial

Returns a function that will append any arguments to the provided initial arguments and call <func>. Syntax is (partial <func> a b c & args). Examples:

  • ((partial +)) -> 0
  • ((partial + 1 2)) -> 3
  • ((partial + 1 2) 3 4 5) -> 15

comp

Takes a set of functions and returns a fn that is the composition of those fns. The returned fn takes a variable number of args, applies the rightmost of fns to the args, the next fn (right-to-left) to the result, etc. When no function is provided, this will be equivalent to calling identity. Examples:

  • ((comp)) -> nil
  • ((comp) 123) -> 123
  • ((comp str +) 1 2 3) -> "6"
  • ((comp (partial * 2) +) 1 2 3) -> 12
  • ((comp (partial + 1) (partial + 1) (partial + 1) (partial + 1) +) 1 2 3) -> 10

map

Returns a lazily evaluated list of executing <func> on each item(s). Syntax is (map <func> <col1> <col2> ...). <func> must accept as many arguments as there are collections provided, as it will be called with the subsequent values of each col, as long as the cols still have values. This means that the length of the resulting list will be the same as the shortest input col. Examples:

  • (map (partial + 1) [1 2 3]) -> [2 3 4]
  • (map + [1 2 3] [1 2 3 4 5 6]) -> [2 4 6]

reduce

f should be a function of 2 arguments. If val is not supplied, returns the result of applying f to the first 2 items in coll, then applying f to that result and the 3rd item, etc. If coll contains no items, f must accept no arguments as well, and reduce returns the result of calling f with no arguments. If coll has only 1 item, it is returned and f is not called. If val is supplied, returns the result of applying f to val and the first item in coll, then applying f to that result and the 2nd item, etc. If coll contains no items, returns val and f is not called. Syntax is (reduce f coll) or (reduce f val coll). Examples:

  • (reduce + []) -> 0
  • (reduce + [12]) -> 12
  • (reduce + [1 2 3 4]) -> 10
  • (reduce + 10 [1 2 3 4]) -> 20

filter

Lazily evaluates coll, returning all items for which (pred item) is True. Examples:

  • (filter (partial > 2) [1 2 3 4 5 6]) -> (3 4 5 6)
  • (filter identity [1 0 true false nil () [2]]) -> (1 0 true [2])

remove

Lazily evaluates coll, returning all items for which (pred item) is False. Examples:

  • (remove (partial > 2) [1 2 3 4 5 6]) -> (1 2)
  • (remove identity [1 0 true false nil () [2]]) -> (1 0 true nil () [2])

keep

Lazily evaluates coll, returning all items for which (pred item) is nil. Examples:

  • (keep (partial > 2) [1 2 3 4 5 6]) -> (1 2 3 4 5 6)
  • (keep identity [1 0 true false nil () [2]]) -> (1 0 true false () [2])

take

Return a lazy sequence of the first n items of coll. Examples:

  • (take 0 [1 2 3 4 5 6]) -> ()
  • (take 3 [1 2 3 4 5 6]) -> (1 2 3)
  • (take 103 [1 2 3 4 5 6]) -> (1 2 3 4 5 6)

take-while

Lazily evaluate coll, returning all items until (pred item) is falsey. Examples:

  • (take-while identity [1 2 3 nil 4 5 6]) -> (1 2 3)
  • (take-while (partial > 5) [1 2 3 4 4 5 6]) -> (1 2 3 4 5)

drop

Return a lazy sequence after dropping the first n items of coll. Examples:

  • (drop 0 [1 2 3 4 5 6]) -> (1 2 3 4 5 6)
  • (drop 3 [1 2 3 4 5 6]) -> (4 5 6)
  • (drop 103 [1 2 3 4 5 6]) -> ()

drop-while

Skip items of coll while (pred item) is truthy, after which return a lazy evaluated list of all the other items of coll. Examples:

  • (drop-while identity [1 2 3 nil 4 5 6]) -> (4 5 6)
  • (drop-while (partial > 5) [1 2 3 4 4 5 6]) -> (6)

range

Returns a lazy seq of nums from start (inclusive) to end (exclusive), by step, where start defaults to 0, step to 1, and end to infinity. When step is equal to 0, returns an infinite sequence of start. When start is equal to end, returns empty list. Syntax is one of:

  • (range)
  • (range end)
  • (range start end)
  • (range start end step)

Examples:

  • (range) -> will return and infinite list of (0 1 2 3 4 ...)
  • (range 10) -> (0 1 2 3 4 5 6 7 8 9)
  • (range 5 10) -> (5 6 7 8 9)
  • (range 2 10 2) -> (2 4 6 8)
  • (range 2 10 2) -> ()
  • (range 10 0 -1) -> (10 9 8 7 6 5 4 3 2 1)
  • (range 2 0 0) -> will return and infinite list of (2 2 2 2 2 2 2 2 ...)

String functions

str

Makes a string by concatenating its arguments. Examples:

  • (str) -> ""
  • (str "abc") -> "abc"
  • (str "abc" "e" "" "fg") -> "abcefg"
  • (str 1 " " 2 " " 3) -> "1 2 3"
  • (str 1 2 3) -> "123"

trim

Removes any whitespace from both sides of a string. Examples:

  • (trim "bla") -> "bla"
  • (trim " bla") -> "bla"
  • (trim "bla \n") -> "bla"
  • (trim " bla ") -> "bla"

upper-case

Returns the provided string as upper case. Examples:

  • (upper-case "abcd") -> "ABCD"
  • (upper-case "aBcD") -> "ABCD"

lower-case

Returns the provided string as lower case. Examples:

  • (lower-case "ABcd") -> "abcd"
  • (lower-case "aBcD") -> "abcd"

Atoms

Atoms are a way to add mutability. By default all data types are immutable - operations on them return new instances with the modified data. Atoms, on the other hand, can be directly changed using the appropriate functions

atom

Create a new atom. Syntax is (atom <value>) Example:

  • (atom 123) - create an atom with the value 123
  • (atom "some string") - create an atom with the value "some string"

deref

Gets the value of an atom. Example:

  • (deref (atom 123)) - will return the value of the new atom, i.e. 123

reset!

Set the value of an atom. Syntax is (reset! <atom> <new value>). Will return the new value. Examples:

  • (reset! (atom 123) 432) - will set the value of the atom to 432, and also return 432
  • (reset! some-atom (upper "bla")) - will set the value of the atom to "BLA" and also return "BLA"

swap!

Sets the value of an atom as the result of calling a function with the atom's current value. Syntax is (swap! <atom> <function> & <args>), where <function> is the function to be called and <args> are optional additional arguments to be provided to the function. The function must accept the atom's value as its first argument. Returns the new value. Example:

  • (swap! (atom "abc") upper-case) - will change the value of the atom to be "ABC". Will return "ABC".
  • (swap! (atom {:a 1}) assoc :a 2) - will change the value of the atom to the result of calling (assoc {:a 1} :a 2), so {:a 2}. Will return {:a 2}

Mathematical Comparisons

+

Returns the sum of all its arguments. Examples:

  • (+) -> 0
  • (+ 1) -> 1
  • (+ 1 2 3 4) -> 10

-

Returns the result of subtracting all its arguments. Examples:

  • (-) -> 0
  • (- 1) -> -1
  • (- 1 -2 3 -4) -> 4

*

Returns the result of multiplying all its arguments. Examples:

  • (*) -> 1
  • (* 1) -> 1
  • (* 1 2 3) -> 6
  • (* 1 2 3 -0.5) -> -3

/

Returns the result of dividing all its arguments. If only one value is provided, it will be used to divide 1. Examples:

  • (/ 10) -> 0.1
  • (/ 20 5) -> 4
  • (/ 30 3 5) -> 2

>

Returns true if each argument, going from left to right, is larger than the next one. Examples:

  • (> 4 3 2 1 0 -1 -2 -3) -> true
  • (> 4 3 1 2) -> false

>=

Returns true if each argument, going from left to right, is larger than or equal to the next one. Examples:

  • (>= 4 3 2 2 2 2 2 2 1 0 -1 -2 -3) -> true
  • (>= 4 3 1 2) -> false

<

Returns true if each argument, going from left to right, is smaller than the next one. Examples:

  • (< -2 -1 0 1 2 3 4) -> true
  • (> 1 3 2 4) -> false

<=

Returns true if each argument, going from left to right, is smaller than or equal to the next one. Examples:

  • (<= -2 -1 0 1 1 1 1 1 2 3 4) -> true
  • (>= 1 3 2 4) -> false

Network calls

These are generic network call functions.

GET

Send a GET request. The syntax is (GET <url> <config>), where <url> is a string with a fully qualified URL, and <config> is a map with the following optional values:

  • :headers - any headers to be sent, as a map of {: }

Examples are:

  • (GET "http://my.service/endpoint") -> will just send a GET request
  • (GET "http://my.service/endpoint" {:headers {"Api-Token" "DEADBEEF"}}) -> will send a GET request with an "Api-Token" header

The response is returned as a map with :status, :headers, :json and :text keys, e.g.:

{
  :status 200
  :headers {"content-length" 33148}
  :json {"response" "bla bla bla"}
  :text "{\"response\": \"bla bla bla\"}"
}

POST

This is the basic function to use when calling external services. The syntax is (POST <url> <config>), where <url> is a string with a fully qualified URL, and <config> is a map with the following optional values:

  • :headers - any headers to be sent, as a map of {: }
  • :json - a structure that should be sent as JSON data
  • :body - a string that will be sent as the body of the request. If :json is provided, this will be ignored

Examples are:

  • (POST "http://my.service/endpoint") -> will just send a POST request with no body
  • (POST "http://my.service/endpoint" {:headers {"Api-Token" "DEADBEEF"}}) -> will send a POST request with no body and an "Api-Token" header
  • A full blown request:
    (POST "http://my.service/endpoint" {
       :headers {
         "Api-Key" "Some key"
         "X-Header" "bla bla"
       }
       :json {
           :type "task"
           :temperature 1
           "max context window" 10000
           :task {
              :text task-text
           }}})
    

The response is returned as a map with :status, :headers, :json and :text keys, e.g.:

{
  :status 200
  :headers {"content-length" 33148}
  :json {"response" "bla bla bla"}
  :text "{\"response\": \"bla bla bla\"}"
}

PUT

This is the basic function to use when calling external services. The syntax is (PUT <url> <config>), where <url> is a string with a fully qualified URL, and <config> is a map with the following optional values:

  • :headers - any headers to be sent, as a map of {: }
  • :json - a structure that should be sent as JSON data
  • :body - a string that will be sent as the body of the request. If :json is provided, this will be ignored

Examples are:

  • (PUT "http://my.service/endpoint") -> will just send a PUT request with no body
  • (PUT "http://my.service/endpoint" {:headers {"Api-Token" "DEADBEEF"}}) -> will send a PUT request with no body and an "Api-Token" header
  • A full blown request:
    (PUT "http://my.service/endpoint" {
       :headers {
         "Api-Key" "Some key"
         "X-Header" "bla bla"
       }
       :json {
           :type "task"
           :temperature 1
           "max context window" 10000
           :task {
              :text task-text
           }}})
    

The response is returned as a map with :status, :headers, :json and :text keys, e.g.:

{
  :status 200
  :headers {"content-length" 33148}
  :json {"response" "bla bla bla"}
  :text "{\"response\": \"bla bla bla\"}"
}

PATCH

This is the basic function to use when calling external services. The syntax is (PATCH <url> <config>), where <url> is a string with a fully qualified URL, and <config> is a map with the following optional values:

  • :headers - any headers to be sent, as a map of {: }
  • :json - a structure that should be sent as JSON data
  • :body - a string that will be sent as the body of the request. If :json is provided, this will be ignored

Examples are:

  • (PATCH "http://my.service/endpoint") -> will just send a PATCH request with no body
  • (PATCH "http://my.service/endpoint" {:headers {"Api-Token" "DEADBEEF"}}) -> will send a PATCH request with no body and an "Api-Token" header
  • A full blown request:
    (PATCH "http://my.service/endpoint" {
       :headers {
         "Api-Key" "Some key"
         "X-Header" "bla bla"
       }
       :json {
           :type "task"
           :temperature 1
           "max context window" 10000
           :task {
              :text task-text
           }}})
    

The response is returned as a map with :status, :headers, :json and :text keys, e.g.:

{
  :status 200
  :headers {"content-length" 33148}
  :json {"response" "bla bla bla"}
  :text "{\"response\": \"bla bla bla\"}"
}

OPTIONS

Send an OPTIONS request. The syntax is (OPTIONS <url> <config>), where <url> is a string with a fully qualified URL, and <config> is a map with the following optional values:

  • :headers - any headers to be sent, as a map of {: }

Examples are:

  • (OPTIONS "http://my.service/endpoint") -> will just send a OPTIONS request
  • (OPTIONS "http://my.service/endpoint" {:headers {"Api-Token" "DEADBEEF"}}) -> will send a OPTIONS request with an "Api-Token" header

The response is returned as a map with :status, :headers, :json and :text keys, e.g.:

{
  :status 200
  :headers {"content-length" 33148}
  :json {"response" "bla bla bla"}
  :text "{\"response\": \"bla bla bla\"}"
}

DELETE

Send a DELETE request. The syntax is (DELETE <url> <config>), where <url> is a string with a fully qualified URL, and <config> is a map with the following optional values:

  • :headers - any headers to be sent, as a map of {: }

Examples are:

  • (DELETE "http://my.service/endpoint") -> will just send a DELETE request
  • (DELETE "http://my.service/endpoint" {:headers {"Api-Token" "DEADBEEF"}}) -> will send a DELETE request with an "Api-Token" header

The response is returned as a map with :status, :headers, :json and :text keys, e.g.: ``` { :status 200 :headers {"content-length" 33148} :json {"response" "bla bla bla"} :text "{\"response\": \"bla bla bla\"}" }

External calls

For this DSL to make sense, it must be able to send tasks to external models. There is a generic POST function that gives the most flexibility, but we also provide vendor specific functions to various popular AI model providers.

OpenAI

Calls the OpenAI endpoint. For this you must provide your API key and the desired model. Syntax is (openai-call <your api key> <model name> <task text>). This call will return the same kind of response as the POST function with OpenAIs response. An example call would be:

  • (openai-call "sk-your-secret-key" "gtp-4-turbo" task-text)

Anthropic

Calls the Anthropic endpoint. For this you must provide your API key and the desired model. Syntax is (anthropic-call <your api key> <model name> <task text>). This call will return the same kind of response as the POST function with Anthropic's response. An example call would be:

  • (anthropic-call "sk-your-secret-key" "claude" task-text)

AWS Bedrock

Calls a AWS Bedrock endpoint. For this you must provide an AWS access and secret key, along with the desired model. Syntax is (bedrock-call <your access key> <your secret key> <model name> <task text>). This call will return the same kind of response as the POST function with AWS's response. An example call would be:

  • (bedrock-call "your-access-key" "your-secret-key" "Jurassic" task-text)

Google VertexAI

Calls a Google VertexAI endpoint. For this you must provide an Google credentials JSON object encoded in base64, along with the desired model. Syntax is (vertexai-call <your credentials> <model name> <task text>). This call will return the same kind of response as the POST function with Google's response. An example call would be:

  • (vertexai-call "ZXhhbXBsZQ==" "chat-bison" task-text)

Mistral

Calls a Mistral endpoint. For this you must provide a Mistral API key, along with the desired model. Syntax is (mistral-call <your api key> <model name> <task text>). This call will return the same kind of response as the POST function with Mistral's response. An example call would be:

  • (mistral-call "your api key" "mistral-small" task-text)

together.ai

Calls a together.ai endpoint. For this you must provide a together.ai API key, along with the desired model. Syntax is (together-ai-call <your api key> <model name> <task text> <optional model type> <optional temperature> <optional max tokens>). The default optional values are: * model type - "completions". Possible values are "completions" or "chat/completions" * temperature - 0.7 * max tokens - 600

This call will return the same kind of response as the POST function with together.ai's response. An example call would be:

  • (together-ai-call "your api key" "google/gemma-7b-it" task-text)

HuggingFace

HuggingFace models have separate calls to spin up an instance, call inference endpoints and then pause the models. You can see more here. To help with this, we provide the following functions:

Setup HuggingFace model

Starts a HuggingFace model. This does not mean that it's available right away, but this call will handle starting it in a idenpotent way, so you can call this function multiple times. Syntax is (setup-hugging-face <api key> <model name> <model config>). An example is:

  • (setup-hugging-face "your key" "model-123" {"name" "bla", "model" {"task" "text-generation"}})
Pause HuggingFace model

Pauses a HuggingFace model. You do not pay for paused models, but you can only have a certain (small) number of models per instance type slot, so e.g. only 8 2xNvidia A100 instances. Once you hit that limit, you'll have to manually delete unwanted ones before you can start another instance. Syntax is (pause-hugging-face <api key> <model config>). An example is:

  • (pause-hugging-face "your key" {"name" "bla", "model" {"task" "text-generation"}})
Call HuggingFace model

Sends a request to a HuggingFace model's inference endpoint. Syntax is (hugging-face-call <api key> <model endpoint url> <model type> <prompt> <num choices>), where:

  • <model endpoint url> is the endpoint to be called - this is generated for each instance and can be found in the HuggingFace console
  • <model type> is the type of model to be evaluated. Should be one of "text-generation", "text2text-generation", "fill-mask", "conversational" or "zero-shot-classification"
  • <prompt> is the task to be evaluated
  • <num choices> is an optional parameter that lets zero-shot-classification models know how many options they have to choose from

An example call is:

  • (hugging-face-call "an api key" "https://huggingface.inference.your.model/endpoint" "text-generation" task-text)

Helper functions

This is a grab bag of various addition functions that are useful, but not standard Clojure functions

to-json

Transforms a DSL data structure into something that can be serialised to JSON. Example calls:

  • (to-json 123) -> 123
  • (to-json {:asd "ads"}) -> {"asd" "ads"}

arg-max

Returns the key with the largest value from the provided map. Example calls:

  • (arg-max {"a" 1 "b" 3 "c" 2}) -> "b"