♊  ✦  Forth Day!  ✦  ♊
══════════════════════════
ESP32forth talks to Gemini
══════════════════════════
    November 16, 2024

GOAL:
  Make an ESP32
   talk to Gemini
    using Forth!

Background
══════════
• ESP32forth (aka µEforth)
  - C implementation of Forth
  - Leverages X-Macros
  - 4 "big" opcodes + many ~1 line opcodes
  - Then bootstraps from Forth code in a string
  - Buildable as an Arduino .ino
  - Optional modules
• Gemini
  - Google's AI Chatbot
  - Multimodal Large Language Model
  - Gemini API

ESP32 Family
════════════
• Espressif Systems
• Built-in WiFi + Bluetooth + more
• ~512KB memory
• ~4MB flash
• Solid Arduino IDE support
• ESP32 / ESP32-S2 / ESP32-S3
  (Tensilica Xtensa LX6/7)
• ESP32-C3 (RISC-V)

@esp32.jpg


@t-dongle-s3.png


             HTTPS         ✦
  ESP32 ──────────────▶ Gemini
             JSON

HTTPS
═════
• Hypertext Transfer Protocol Secure
• Wrap TCP connect with Transport Layer Security
• Encrypts communication with a symmetric cipher
  (usually AES)
• Key exchange and establishing a chain of trust
  with an asymmetric cipher
  (usually RSA or Elliptic Curve Diffie-Hellman)
  - Resists man-in-the-middle attacks
  - Requires Certificate Authority Roots of Trust

HTTPClient
══════════
• HTTP / HTTPS Arduino library
• Seems to check chain of trust
• Handles HTTP headers and streaming connections
• Supports connection reuse
• Added to ESP32forth as an "optional" module

Y(name, action) \
X("name", C_NAME, action) \
YV(vocabulary, name, action) \
XV(vocabulary, "name", C_NAME, action) \
  
n10 n9 n8 n7 n6 n5 n4 n3 n2 n1 n0 - Access stack as cell_t integer values
                   c4 c3 c2 c1 c0 - Access stack as char* values
                   b4 b3 b2 b1 b0 - Access stack as uint8_t* byte values
                   a4 a3 a2 a1 a0 - Access stack as void* values
 
PUSH val;
DROP; DROPn(n); 
NIP; NIPn(n);

NetworkClientSecure.new ( -- nclient )
NetworkClientSecure.delete ( nclient -- )
NetworkClientSecure.setCACert ( certz nclient -- )

#define OPTIONAL_HTTP_CLIENT_SUPPORT \
  XV(HTTPClient, "NetworkClientSecure.new", \
      NetworkClientSecure_new, PUSH new NetworkClientSecure()) \
  XV(HTTPClient, "NetworkClientSecure.delete", \
      NetworkClientSecure_delete, delete ((NetworkClientSecure *) a0); DROP) \
  XV(HTTPClient, "NetworkClientSecure.setCACert", \
      NetworkClientSecure_setCACert, \
      ((NetworkClientSecure *) a0)->setCACert(c1); DROPn(2)) \

openssl s_client -showcerts -connect generativelanguage.googleapis.com:443
══════════════════════════════════════════════════════════════════════════
CONNECTED(00000003)
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services, CN = WR2
verify return:1
depth=0 CN = upload.video.google.com
verify return:1
---
Certificate chain
 0 s:CN = upload.video.google.com
   i:C = US, O = Google Trust Services, CN = WR2
-----BEGIN CERTIFICATE-----
MIIF4TCCBMmgAwIBAgIRAKbTPFDkoO+rCl1CyvNePccwDQYJKoZIhvcNAQELBQAw
....
zZcc0fBNWHsCRIMISrxV5JWe9CX2xlc1u/RZHlJiwhVwQM5n2fsjIzDKxacZD6vY
OFZdBiD3MNlxnEe9bDKclMV0LLZO
-----END CERTIFICATE-----
 1 s:C = US, O = Google Trust Services, CN = WR2
   i:C = US, O = Google Trust Services LLC, CN = GTS Root R1
-----BEGIN CERTIFICATE-----
MIIFCzCCAvOgAwIBAgIQf/AFoHxM3tEArZ1mpRB7mDANBgkqhkiG9w0BAQsFADBH
....
/oTxUFqOl2stKnn7QGTq8z29W+GgBLCXSBxC9epaHM0myFH/FJlniXJfHeytWt0=
-----END CERTIFICATE-----
 2 s:C = US, O = Google Trust Services LLC, CN = GTS Root R1
   i:C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX   ◀─────────
....
d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=
-----END CERTIFICATE-----
---

-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE
CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx
OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT
GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63
ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS
iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k
KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ
DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk
j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5
cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW
CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499
iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei
Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap
sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b
9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf
BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw
JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH
MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al
oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy
MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF
AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9
NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9
WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw
9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy
+qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi
d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=
-----END CERTIFICATE-----

HTTPClient.new ( -- hclient )
HTTPClient.delete ( hclient -- )
HTTPClient.begin ( url hclient -- err )
HTTPClient.beginNC ( url nclient hclient -- err )
HTTPClient.end ( hclient -- )
HTTPClient.connected ( hclient -- f )
  
HTTPClient.setReuse ( f hclient -- )
HTTPClient.setUserAgent ( agent hclient -- )
HTTPClient.setAuthorization ( username password hclient -- )
HTTPClient.setFollowRedirects ( f hclient -- )
HTTPClient.setRedirectLimit ( n hclient -- )

HTTPClient.GET ( hclient -- code )
HTTPClient.POST ( a n hclient -- code )
HTTPClient.sendRequest ( methodz a n hclient -- code )
HTTPClient.getSize ( hclient  -- n )
HTTPClient.getString ( a n hclient -- )
HTTPClient.getStreamPtr ( hclient -- stream )
  
NetworkClient.available ( stream -- n )
NetworkClient.readBytes ( a n stream -- n )

JSON
════
• JavaScript Object Notation
• Simple types:
  - Float64 (covers int32 range also)
  - String
  - Boolean (true, false)
  - null
• Complex types:
  - Array [items...]
  - Object / Dictionary { key: value, ... }
• Good format for protocol messages
  - Human readable, reasonably compact
  - Self describing
  - Schema-less

{
  "mykey": [
    1,
    2,
    3,
    4,
    5
  ],
  "key2": {
    "red": "#ff0000",
    "green": "#00ff00",
    "blue": "#0000ff"
  }
}

But in Forth?
═════════════
• Need a way to represent nest structures
  - But doesn't need to support cycles
  - Can be reference counted!
• Forth lets us build new types + stacks
• How about APL / CoSy style ARRAYS?

Arrays are all you need?
════════════════════════
• Wisdom of APL:
  - Offers dense chunks of like typed values
  - Fall back to MIXED references to other arrays
• Makes things uniform
• Supports a pattern to promote types as needed
• Dictionaries...

128 constant stack-depth
 
( Stack for arrays )
create astack    stack-depth cells allot
variable ap    astack ap !
: apush ( a -- ) cell ap +! ap @ ! ;
: apop ( -- a ) ap @ @  cell negate ap +! ;
: top ( -- a ) ap @ @ ;
: under ( -- a ) ap @ cell - @ ;
 
( Secondary stack for arrays )
create arstack   stack-depth cells allot
variable arp   arstack arp !
: >a   apop   cell arp +! arp @ ! ;
: a>   arp @ @  cell negate arp +! apush ;

┌─────────────────┬──────┬─────────────┬───────···
│ Reference Count │ Size │     Type    │ Data  ···
└─────────────────┴──────┴─────────────┴───────···
                                        ^ 
┌─────────────────┬──────┬─────────────┬─────────────────···
│ Reference Count │ Size │ 0 (MIXED)   │ Array0, Array1, ···
└─────────────────┴──────┴─────────────┴─────────────────···
                                        ^
┌─────────────────┬──────┬─────────────┬─────────────────···
│ Reference Count │ Size │ 1 (STRING)  │ Char0, Char1,   ···
└─────────────────┴──────┴─────────────┴─────────────────···
                                        ^ 
┌─────────────────┬──────┬─────────────┬─────────────────···
│ Reference Count │ Size │ 2 (INTEGER) │ Int0, Int1, Int2···
└─────────────────┴──────┴─────────────┴─────────────────···
                                        ^ 
┌─────────────────┬──────┬─────────────┬─────────────────···
│ Reference Count │ Size │ 3 (REAL)    │ Float0, Float1  ···
└─────────────────┴──────┴─────────────┴─────────────────···
                                        ^

( Array types )
0 constant MIXED
1 constant STRING
2 constant INTEGER
3 constant REAL
create array-sizes   cell , 1 , cell , sfloat ,
: >esize ( type -- n ) cells array-sizes + @ ;
 
( ref n type ^data... )
3 cells constant header-size
: >type ( a -- a ) -1 cells + ;
: >count ( a -- a ) -2 cells + ;
: >ref ( a -- a ) -3 cells + ;

( Size of array data in bytes )
: bytes ( a -- n ) dup >type @ >esize swap >count @ * ;
( To string / array )
: range ( a -- a n ) dup >count @ ;
  
( Create an uninitialized array )
: array ( n type -- a: a )
   2dup >esize * header-size + allocate throw header-size + apush
   top >type !   top >count !   0 top >ref ! ;

( Reference counting for arrays )
: ref ( a -- ) 1 over >ref +! ;
: unref ( a -- )
   dup 0= if drop exit then
   -1 over >ref +!
   dup >ref @ 0< if
     dup >type @ MIXED = if
       dup dup >count @ 0 ?do
         dup @ recurse cell+
       loop
       drop 
     then
     header-size - free throw exit
   then drop ;

( Stack manipulation )
: adrop ( a: a -- ) apop unref ;
: a2drop ( a: a a -- ) adrop adrop ;
: anip ( a: a b -- b ) apop apop unref apush ;
: adup ( a: a -- a a ) top ref apush ;
: aswap ( a: a b -- b a ) apop apop swap apush apush ;
: aover ( a: a b -- a b a ) apop apop ref dup apush swap apush apush ;
: a2dup ( a: a b -- a b a b ) aover aover ;
  
( Index into the top of the stack )
: a@ ( n a: a -- a: a ) cells top + @ ref adrop apush ;

( Raw array creation words )
: empty ( -- a: a ) 0 MIXED array ;
: box ( a: a -- a ) apop 1 MIXED array top ! ;
: _s ( a n -- a: a ) dup STRING array top swap cmove ;
: _c ( ch -- a: a ) 1 STRING array top c! ;
: _i ( n -- a: a ) 1 INTEGER array top ! ;
: _f ( f: n -- a: a ) 1 REAL array top sf! ;
: _s"   postpone s" state @ if postpone _s else _s then ; immediate
  
: aconstant   create apop , does> @ ref apush ;
  
( Building arrays on the stack. )
: [[   ap @ ;
: ]]   ap @ swap - cell/ empty for aft aswap box aswap ,c then next ;

( Convert integer array to floats )
: n>f
   top >count @ REAL array
   under top range 0 ?do over @ s>f dup sf! sfloat+ >r cell+ r> loop 2drop anip ;
  
( Force integers to real. )
: binuminal
   top >type @ INTEGER = under >type @ REAL = and if n>f then
   under >type @ INTEGER = top >type @ REAL = and if apop n>f apush then
;

0 value layer
: lst ( a -- )
   layer spaces
   dup >type @ case
     MIXED of
       ." [" cr
       2 +to layer
       dup >count @ 0 ?do
         dup @ recurse cell+ cr
       loop
       drop
       -2 +to layer
       layer spaces ." ]"
     endof
     STRING of dup >count @ type endof
     INTEGER of dup >count @ 0 ?do dup @ . cell+ loop drop endof
     REAL of dup >count @ 0 ?do dup sf@ f. sfloat+ loop drop endof
   endcase
;
: a. ( a -- ) top lst adrop ;

: catenate ( a: a a -- a ) ( catenate )
  binuminal
  top >type @ under >type @ = if
    under >count @ top >count @ + top >type @ array apop >r
    under r@ under bytes cmove
    top r@ under bytes + top bytes cmove
    under under bytes 0 fill
    top top bytes 0 fill
    r> apush anip anip
    exit
  then
  top >type @ MIXED = if apop box apush recurse exit then
  under >type @ MIXED = if box recurse exit then
  apop apop 2 MIXED array top cell+ ! top !
;
: ,c   catenate ;

That's enough for JSON!

Parsing JSON
════════════
• Recursive descent parser
  - Code mirrors grammar
• Single character lookahead

defer getchar
-1 value token
: skip   getchar to token ;
 
variable insource
variable inlength
: in ( a n -- ) inlength ! insource ! skip ;
: ingetchar ( -- n )
   inlength @ 0= if -1 exit then
   insource @ c@ 1 insource +! -1 inlength +! ;
' ingetchar is getchar
 
: expect ( a n -- )
  for aft dup c@ token = assert 1+ skip then next drop ;
: sliteral ( a n -- ) postpone $@ dup , zplace ;
: e:   bl parse sliteral postpone expect ; immediate

                       ╭──string──╮
                       │          │
                       ├──number──┤
                       │          │
value                  ├──object──┤
  ────────whitespace───┤          ├───whitespace─────▶
                       ├──array───┤
                       │          │
                       ├──'true'──┤
                       │          │
                       ├─'false'──┤
                       │          │
                       ╰──'null'──╯

defer <value>
  ....
:noname
   <whitespace>
   token case
     [char] " of <string> endof
     [char] { of <object> endof
     [char] [ of <array> endof
     [char] t of e: true true endof
     [char] f of e: false false endof
     [char] n of e: null null endof
     <number>
   endcase
   <whitespace>
; is <value>

s" null" _s aconstant null
s" true" _s aconstant true
s" false" _s aconstant false

                  ╭───────────────────╮
                  │                   │
                  ├─────linefeed──────┤
                  │                   │
                  ├──carriage return──┤
                  │                   │
                  ├────────tab────────┤
whitespace        │                   │
  ────────────╭───┤                   ├────────▶
              │   │                  ╭╯
              │   ╰───────space──────┤
              │                      ╰╮ 
              ╰───────────────────────╯

: space? ( ch -- f )
   dup 8 =
   over 10 = or
   over 13 = or
   swap 32 = or ;
: <whitespace> begin token space? while skip repeat ;

array         ╭────whitespace────╮
  ────────[───┤                  ├───]─────▶
              │                 ╭╯
      ╭──,────╰──────value──────┤
      │                         ╰╮ 
      ╰──────────────────────────╯

: <array>
   e: [ <whitespace>
   0 MIXED array
   begin
     token [char] ] = if skip exit then
     <value> box catenate <whitespace>
     token [char] ] = if skip exit then
     e: , <whitespace>
   again
;

object        ╭───────────────────whitespace─────────────────╮
  ────────{───┤                                              ├───}─────▶
              │                                             ╭╯
      ╭──,────╰───whitespace──string──whitespace──:──value──┤
      │                                                     ╰╮ 
      ╰──────────────────────────────────────────────────────╯

: <object>
   e: { <whitespace>
   DICT box
   begin
     token [char] } = if skip exit then
     <string> box <whitespace> e: : <whitespace> <value> box
     catenate box catenate
     token [char] } = if skip exit then
     e: , <whitespace>
   again
   e: }
;

                  ╭──────────────────╮
                  │                  │
                  ├────not \ or "────┤
string            │                  │
  ────────"───╭───┤                  ├───"─────▶
              │   │                 ╭╯
              │   ╰─────escaped─────┤
              │                     ╰╮ 
              ╰──────────────────────╯

: <string>
   e: " s" " _s
   begin token [char] " <> while
     token [char] \ = if
       <escaped>
     else
       token _c catenate
       skip
     then
   repeat
   e: "
;

                 ╭───"───╮
                 │       │
                 ├───\───┤
                 │       │
escaped          ├───/───┤
  ────────────\──┤       ├──────────────────────────▶
                 ├───b───┤backspace
                 │       │
                 ├───f───┤formfeed
                 │       │
                 ├───n───┤nl
                 │       │
                 ├───r───┤cr
                 │       │
                 ├───t───┤tab
                 │       ╰───────────────╮
                 │                       │
                 ╰───u────4_hex_digits───╯

: <escaped>
   e: \
   token skip case
     [char] " of [char] " _c catenate endof
     [char] \ of [char] \ _c catenate endof
     [char] / of [char] / _c catenate endof
     [char] b of 8 _c catenate endof
     [char] f of 12 _c catenate endof
     [char] n of nl _c catenate endof
     [char] r of 13 _c catenate endof
     [char] t of 9 _c catenate endof
     [char] u of 255 _c catenate skip skip skip skip endof
     -1 throw
   endcase
;

number
══════
[-]888               -- mantissa no-leading zeros
      [.888]         -- fractional part
            [E+888]
            [E-888]  -- exponent
            [E888]

: digit? ( -- f ) token [char] 0 >= token [char] 9 <= and ;
: <digit>   token [char] 0 - skip ;
: <integer>   digit? assert <digit>
              begin digit? while 10 * <digit> + repeat ;
: <fraction>   digit? assert 10 * <digit> + >r 1- r>
               begin digit? while 10 * <digit> + >r 1- r> repeat ;
: <number>
  token [char] - = if skip -1 else 1 then
  token [char] 0 = if skip 0 else <integer> then 0 swap
  token [char] . = if skip <fraction> then
  swap >r * r>
  token [char] e = token [char] E = or if
    skip
    token [char] - = if
      skip -1
    else
      token [char] + = if skip then 1
    then
    <integer> * +
  then
  dup if 10e s>f f** s>f f* _f else drop _i then
;

: json> ( a -- a ) top range in <value> anip ;

What about printing JSON?

: butlast? ( n -- f ) top >count @ 1- <> ;
: escaped ( a n -- a: a )
  _s" "
  0 ?do
    dup i + c@
    case
      [char] " of _s" \" ,c [char] " _c ,c endof
      [char] / of _s" \/" ,c endof
      [char] \ of _s" \\" ,c endof
      8 of _s" \b" ,c endof
      12 of _s" \f" ,c endof
      nl of _s" \n" ,c endof
      13 of _s" \r" ,c endof
      9 of _s" \t" ,c endof
      dup _c ,c
    endcase
  loop
  drop
;

: >json ( a: a -- a )
  top >type @ case
    MIXED of
      top >count @ 1 > if top @ DICT top adrop = else 0 then if
        _s" {" >a top >count @ 1 ?do
          adup i a@ 0 a@ recurse _s" :" ,c a> aswap ,c >a
          adup i a@ 1 a@ recurse a> aswap ,c >a
          i butlast? if a> _s" ," ,c >a then
        loop a> _s" }" ,c
      else
        _s" [" >a top >count @ 0 ?do
          adup i a@ recurse a> aswap ,c >a
          i butlast? if a> _s" ," ,c >a then
        loop a> _s" ]" ,c
      then
    endof
    STRING of
      top null top adrop = if exit then
      top true top adrop = if exit then
      top false top adrop = if exit then
      [char] " _c >a top range a> escaped ,c [char] " _c ,c
    endof
    INTEGER of
      _s" " >a
      top >count @ 0 ?do
        top i cells + @ <# #s #> a> _s"  " ,c _s ,c >a
      loop a> endof
    REAL of
      _s" " >a top >count @ 0 ?do
        top i sfloats + sf@ <# #fs #> a> _s" " ,c _s ,c >a
      loop a> endof
  endcase
  anip
;

Dictionaries
════════════
• Need to represent key, value pairs
• Need to be able to tell them apart
  - Use address equality!
• Ideally, fast, but meh

[
  "DICTIONARY"
  [key1, value1]
  [key2, value2]
  ...
]

s" DICTIONARY" _s aconstant DICT
: {{   [[ DICT ;
: }}   ]] ;

( Dictionary lookup )
: as= ( a: a a -- f )
  top >type @ STRING <> if a2drop 0 exit then
  under >type @ STRING <> if a2drop 0 exit then
  top range under range str= a2drop
;
: dict@ ( a: a key -- value )
  aswap
  top >count @ 1 ?do
    a2dup i a@ 0 a@ as= if i a@ 1 a@ anip unloop exit then
  loop
  a2drop
  null
;

Gemini API
══════════
• Transport
  - REST API (Representational State Transfer)
  - Send JSON, Get JSON
• Features
  - Fine-tuning for a task
  - Structured Output
  - Grounding with Google Search
  - Function calling
  - Document, Audio, and Image input
  - Image output
  - System Intructions (who to reply as)
  - Long context windows (>99.7% recall up to 1M tokens)

POST to:
  https://generativelanguage.googleapis.com/v1beta/models
        /gemini-1.5-flash:generateContent?key=GOOGLE_API_KEY
This:
{
  "contents": [
    {
      "parts":[
         {"text": "Write a story about a magic backpack."}
      ]
    }
  ]
}

Result
══════
{
  "candidates": [
    {
      "content": {
        "parts": [
          {
────────▶  "text": "Lila had always been a dreamer. ...."
          }
        ],
        "role": "model"
      },
      "finishReason": "STOP",
      "index": 0,
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 8,
    "candidatesTokenCount": 570,
    "totalTokenCount": 578
  },
  "modelVersion": "gemini-1.5-flash-001"
}

Parts
═════
{
  "contents": [
    {"role":"user", "parts":[{
      "text": "Hello"
    }]},
    {"role": "model", "parts":[{
       "text": "Great to meet you. What would you like to know?"
    }]},
    {"role":"user", "parts":[{
       "text": "I have two dogs in my house. How many paws are in my house?"}]},
  ]
}

Parameters
══════════
{
  ...
  "safetySettings": [
    {
      "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
      "threshold": "BLOCK_ONLY_HIGH"
    }
  ],
  "generationConfig": {
    "stopSequences": [
      "Title"
    ],
    "temperature": 1.0,
    "maxOutputTokens": 800,
    "topP": 0.8,
    "topK": 10
  }
}

: slurp ( a: filename -- a: content )
  top range r/o open-file throw >r adrop
  r@ file-size throw STRING array
  top range r@ read-file throw drop
  r> close-file throw
;

: 0,c ( a -- a\0 ) 0 _c ,c ;
 
_s" /spiffs/gemini_cert" slurp 0,c aconstant cacert
 
_s" https://generativelanguage.googleapis.com
     /v1beta/models/gemini-1.5-flash:generateContent?key="
  _s" /spiffs/gemini_key" slurp json> _s" key" dict@ ,c 0,c aconstant url
 
also HTTPClient
NetworkClientSecure.new constant nclient
cacert top adrop nclient NetworkClientSecure.setCACert

{
  "key": "My Big Secret!"
}

@console.png


: askit ( a: a -- a )
   >a
   {{
     [[ _s" contents" [[
       {{
         [[ _s" parts" [[
           {{
             [[ _s" text" a> ]]
           }}
         ]] ]]
       }}
     ]] ]]
   }}
;
   
: reply-text ( a: a -- a )
  json> _s" candidates" dict@ 0 a@ _s" content" dict@ _s" parts" dict@ 0 a@ _s" text" dict@
;
  
: snipped ( a -- a ) top 3 + top >count @ 3 - _s anip ;
 
: ask
  nl parse _s askit >json doquery
  snipped reply-text a. cr ;

: askjson ( a -- a )
   >a {{
     [[ _s" contents" [[
       {{
         [[ _s" parts" [[
           {{
             [[ _s" text" a> ]]
           }}
         ]] ]]
       }}
     ]] ]]
     [[ _s" generationConfig" {{
       [[ _s" response_mime_type" _s" application/json" ]]
     }} ]]
   }}
;

: asklist ( a -- a )
  _s"  using this JSON schema: list[str]" ,c askjson >json doquery
  snipped reply-text json> ;

  DEMO &
QUESTIONS❓
    🙏
 Thank you!