♊ ✦ 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!