~/bend-docscommunity

llm.bend relies on unsafe/foreign

raw source on the hub · import bend-kit-llm@0.3.0.0/llm.bend as Llm

LLM client for the Anthropic Messages and OpenAI Chat Completions APIs, with SSE streaming. Source: https://github.com/paymog/bend-kit/tree/main/llm

8 imports
import Base
import ./sse.bend as Sse
import bend-kit-hairpin@0.3.0.0/hairpin.bend as Hairpin
import bend-kit-hairpin@0.3.0.0/retry.bend as Retry
import bend-kit-http@0.32.0.0/http.bend as Http
import 0x584fc27920487ceab242392391418d7f/json.bend as Json
import 0x49814d83de8f70993a43e1002be29ecd/bytes.bend as Bytes
import 0xcfc8be7b076f41f95c8e118383892d55/encoding.bend as Enc

Types

type Api source · line 13 · raw

Data

Anthropic: POST {base}/v1/messages. OpenAI: POST {base}/chat/completions, which many other providers serve too.

type Role source · line 17 · raw

Data

type Msg source · line 21 · raw

Data

type Req source · line 25 · raw

Data

system: "" for none. max: the most tokens to write.

type Reply source · line 29 · raw

Data

stop: the API's reason, as "end_turn" or "stop". input and output count tokens.

type Err source · line 34 · raw

Data

ErrNet: the request did not complete, or Hairpin's breaker admitted no attempt. ErrApi: the API refused, with its status and error text; an error event in a stream has status 200. ErrReply: a reply that is not the API's JSON, with its text.

type Client source · line 40 · raw

Type

headers go on every request. ms: the step timeout. retries: extra tries after a retryable failure.

type Leaf source · line 103 · raw

Data

A JSON leaf: its path, as "content.0.text", and its text.

type Ev source · line 217 · raw

Data

A text piece, the end, an error, or an event with no text.

type Stream source · line 412 · raw

Type

type Rn source · line 541 · raw

Type

The reader after the next event, or the stream's result.

Definitions

def str source · line 45 · raw

@s:String -> 0x584fc27920487ceab242392391418d7f/json.Val

def kv source · line 48 · raw

@k:String -> @v:0x584fc27920487ceab242392391418d7f/json.Val -> Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)

def num source · line 51 · raw

@+n:U32 -> 0x584fc27920487ceab242392391418d7f/json.Val

def obj source · line 54 · raw

@kvs:List<&1, Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)> -> 0x584fc27920487ceab242392391418d7f/json.Val

def when source · line 57 · raw

@on:Bool -> @x:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val) -> @xs:List<&1, Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)> -> List<&1, Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)>

def role source · line 64 · raw

@r:Role -> String

def msg source · line 71 · raw

@+r:String -> @t:String -> 0x584fc27920487ceab242392391418d7f/json.Val

def msgs source · line 74 · raw

@xs:List<&2, Msg> -> List<&1, 0x584fc27920487ceab242392391418d7f/json.Val>

def sys source · line 81 · raw

@on:Bool -> @s:String -> @xs:List<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> List<&1, 0x584fc27920487ceab242392391418d7f/json.Val>

def body source · line 89 · raw

@api:Api -> @req:Req -> @stream:Bool -> 0x584fc27920487ceab242392391418d7f/json.Val

The request body for api. stream asks for SSE.

def path source · line 106 · raw

@+p:String -> @+k:String -> String

def push.arr source · line 109 · raw

@xs:List<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> @+p:String -> @+i:U32 -> @t:List<&1, Pair(String, 0x584fc27920487ceab242392391418d7f/json.Val)> -> List<&1, Pair(String, 0x584fc27920487ceab242392391418d7f/json.Val)>

def push.obj source · line 116 · raw

@kvs:List<&1, Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)> -> @+p:String -> @t:List<&1, Pair(String, 0x584fc27920487ceab242392391418d7f/json.Val)> -> List<&1, Pair(String, 0x584fc27920487ceab242392391418d7f/json.Val)>

def flat source · line 124 · raw

@f:Nat -> @stack:List<&1, Pair(String, 0x584fc27920487ceab242392391418d7f/json.Val)> -> @acc:List<&2, Leaf> -> List<&2, Leaf>

The leaves under the stack's values, last first. f: at least the values left.

def doc.of source · line 147 · raw

@+n:U32 -> @m:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> Maybe<&2, List<&2, Leaf>>

def doc source · line 156 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Maybe<&2, List<&2, Leaf>>

Every non-null leaf of a JSON text in document order, keyed by its path, as ("content.0.text", "Hi"). Strings are decoded; numbers keep their text. None when b is not JSON. Each value takes a byte, so n + 1 steps suffice.

def look source · line 161 · raw

@d:List<&2, Leaf> -> @+k:String -> String

The first value at path k, or "".

def count source · line 168 · raw

@s:String -> U32

def dots source · line 171 · raw

@s:String -> @+n:U32 -> U32

def block source · line 179 · raw

@+p:String -> Bool

Is p the text of a content block, "content.<i>.text"?

def blocks source · line 183 · raw

@d:List<&2, Leaf> -> String

The text blocks of an Anthropic message, joined.

def reply.of source · line 190 · raw

@api:Api -> @+d:List<&2, Leaf> -> Reply

def reply.got source · line 199 · raw

@api:Api -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @m:Maybe<&2, List<&2, Leaf>> -> Result<&1, &1, Err, Reply>

def reply.copy source · line 206 · raw

@api:Api -> @r:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> Result<&1, &1, Err, Reply>

def reply source · line 211 · raw

@api:Api -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, Reply>

A 2xx response body as a Reply.

def ev.text source · line 223 · raw

@+s:String -> Ev

def ev.doc source · line 226 · raw

@m:Maybe<&2, List<&2, Leaf>> -> List<&2, Leaf>

def ev.anthropic source · line 229 · raw

@+name:String -> @data:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Ev

def ev.openai source · line 235 · raw

@+s:String -> Ev

def ev source · line 242 · raw

@api:Api -> @e:0xce2a0a9d4864ec82024ab27469071032/sse.Message -> Ev

What one SSE event of api means.

def ok source · line 253 · raw

@+s:U32 -> Bool

def again.status source · line 257 · raw

@+s:U32 -> Bool

Statuses a later try may clear: 408, 409, 429, and 5xx, which holds Anthropic's 529.

def again.err source · line 261 · raw

@+e:0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err -> Maybe<&2, String>

A refused connect or a timeout may clear.

def again.of source · line 270 · raw

@+status:U32 -> @+h:Map<&2, List<&2, String>> -> Maybe<&2, String>

def again source · line 274 · raw

@r:Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Pair(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>, Maybe<&2, String>)

The result, with Some{Retry-After} when another try may help. Hairpin's judge for every call.

def body.ok source · line 282 · raw

@good:Bool -> @+status:U32 -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

def body.of source · line 290 · raw

@r:Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

The 2xx body, or why there is none.

def url source · line 298 · raw

@api:Api -> @+base:String -> String

def new source · line 307 · raw

@+api:Api -> @base:String -> @headers:Map<&2, List<&2, String>> -> Client

def json source · line 310 · raw

@h:Map<&2, List<&2, String>> -> Map<&2, List<&2, String>>

def anthropic source · line 314 · raw

@key:String -> Client

base https://api.anthropic.com, 10 min steps, 2 retries.

def openai source · line 318 · raw

@key:String -> Client

base https://api.openai.com/v1, 10 min steps, 2 retries.

def base source · line 322 · raw

@c:Client -> @url:String -> Client

Another base URL, as for a provider that serves the same API.

def timeout source · line 331 · raw

@c:Client -> @+ms:U32 -> Client

def retry source · line 335 · raw

@c:Client -> @n:U32 -> Client

def close source · line 339 · raw

@c:Client -> IO(Unit)

def call.done source · line 343 · raw

@+api:Api -> @base:String -> @headers:Map<&2, List<&2, String>> -> @+ms:U32 -> @+n:U32 -> @x:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(0x5f997a9351e7133e43ba9611e734f8a6/retry.Budget, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>)) -> Pair(Client, Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>)

def call.budget source · line 348 · raw

@+api:Api -> @+base:String -> @+headers:Map<&2, List<&2, String>> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @y:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, 0x5f997a9351e7133e43ba9611e734f8a6/retry.Budget) -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(0x5f997a9351e7133e43ba9611e734f8a6/retry.Budget, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>)))

def call source · line 357 · raw

@c:Client -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(Client, Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>))

POST body to the API with n retries on a budget of n + 1; the 2xx body comes back. These POSTs are safe to repeat, so Hairpin retries them as again judges.

def raw.got source · line 364 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @m:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>

def raw.copy source · line 371 · raw

@r:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>

def raw.of source · line 375 · raw

@r:Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>

def raw.done source · line 382 · raw

@x:Pair(Client, Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>) -> Pair(Client, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)

def send.raw source · line 387 · raw

@c:Client -> @v:0x584fc27920487ceab242392391418d7f/json.Val -> IO(Pair(Client, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>))

Any request body, as for tools or images, and the reply as JSON.

def send.of source · line 392 · raw

@r:Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> @+api:Api -> Result<&1, &1, Err, Reply>

def send.done source · line 399 · raw

@+api:Api -> @x:Pair(Client, Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>) -> Pair(Client, Result<&1, &1, Err, Reply>)

def send source · line 404 · raw

@c:Client -> @req:Req -> IO(Pair(Client, Result<&1, &1, Err, Reply>))

One whole reply.

def open.res source · line 415 · raw

@x:Pair(0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res) -> Pair(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>, Maybe<&2, String>)

def open.judge source · line 420 · raw

@r:Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream> -> Pair(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>, Maybe<&2, String>)

def open.kept source · line 427 · raw

@body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @j:Pair(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>, Maybe<&2, String>) -> Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, Pair(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>, Maybe<&2, String>))

def open.once source · line 431 · raw

@+url:String -> @+headers:Map<&2, List<&2, String>> -> @+ms:U32 -> @r:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> IO(Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, Pair(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>, Maybe<&2, String>)))

def open.drop source · line 438 · raw

@r:Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream> -> IO(Unit)

def open.go source · line 446 · raw

@left:Nat -> @+n:Nat -> @+url:String -> @+headers:Map<&2, List<&2, String>> -> @+ms:U32 -> @t:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, Pair(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>, Maybe<&2, String>)) -> IO(Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>)

A refused stream closes before the next try.

def drain source · line 465 · raw

@f:Nat -> @x:Pair(0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream, Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>>) -> @acc:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes)

A refused stream's error body, at most f more pieces, then the stream closed.

def start.refused source · line 490 · raw

@+status:U32 -> @h:0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream -> IO(Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>)

def start.ok source · line 496 · raw

@good:Bool -> @+status:U32 -> @h:0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream -> IO(Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>)

def start.res source · line 503 · raw

@x:Pair(0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res) -> IO(Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>)

def start source · line 508 · raw

@r:Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream> -> IO(Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream>)

def stream.of source · line 515 · raw

@r:Result<&1, &1, Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream> -> @+api:Api -> Result<&1, &1, Err, Stream>

def stream.raw source · line 523 · raw

@c:Client -> @v:0x584fc27920487ceab242392391418d7f/json.Val -> IO(Pair(Client, Result<&1, &1, Err, Stream>))

Any request body that asks for SSE, as for tools. A stream opens its own connection, outside the pool.

def stream source · line 532 · raw

@c:Client -> @req:Req -> IO(Pair(Client, Result<&1, &1, Err, Stream>))

The reply as it is written: read it with next.

def stream.close source · line 536 · raw

@s:Stream -> IO(Unit)

def event.read source · line 545 · raw

@rd:0xce2a0a9d4864ec82024ab27469071032/sse.Reader -> @x:Pair(0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream, Result<&1, &1, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Err, Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>>) -> Rn

def event.loop source · line 558 · raw

@f:Nat -> @st:Rn -> IO(Pair(0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream, Pair(0xce2a0a9d4864ec82024ab27469071032/sse.Reader, Result<&1, &1, Err, Maybe<&1, 0xce2a0a9d4864ec82024ab27469071032/sse.Message>>)))

f: pieces left to read before the stream counts as stalled.

def event.done source · line 581 · raw

@+api:Api -> @x:Pair(0x6a946b742ab6f76f0ba5b90b14da52ad/http.Stream, Pair(0xce2a0a9d4864ec82024ab27469071032/sse.Reader, Result<&1, &1, Err, Maybe<&1, 0xce2a0a9d4864ec82024ab27469071032/sse.Message>>)) -> Pair(Stream, Result<&1, &1, Err, Maybe<&1, 0xce2a0a9d4864ec82024ab27469071032/sse.Message>>)

def event source · line 586 · raw

@s:Stream -> IO(Pair(Stream, Result<&1, &1, Err, Maybe<&1, 0xce2a0a9d4864ec82024ab27469071032/sse.Message>>))

The next SSE event, or None when the stream ends. An event the end cuts off is dropped.

def next.ev source · line 592 · raw

@r:Result<&1, &1, Err, Maybe<&1, 0xce2a0a9d4864ec82024ab27469071032/sse.Message>> -> @+api:Api -> Ev

def next.of source · line 603 · raw

@x:Pair(Stream, Result<&1, &1, Err, Maybe<&1, 0xce2a0a9d4864ec82024ab27469071032/sse.Message>>) -> Pair(Stream, Ev)

def next.loop source · line 609 · raw

@f:Nat -> @x:Pair(Stream, Ev) -> IO(Pair(Stream, Result<&1, &1, Err, Maybe<&2, String>>))

f: events left to skip.

def next source · line 629 · raw

@s:Stream -> IO(Pair(Stream, Result<&1, &1, Err, Maybe<&2, String>>))

The next piece of reply text, or None at the end.